ApacheのRewriteCondは、直後のRewriteRuleを実行する条件を指定するdirectiveです。URL pathだけでなく、host、query string、request method、実在file・directoryなどを条件にできます。
基本形は次の3行です。
RewriteEngine On
RewriteCond "%{REQUEST_FILENAME}" !-f
RewriteRule "^" "index.php" [END]
この例では、requestに対応する実在fileがない場合だけ、直後のRewriteRuleを実行します。ただし、front controllerだけが目的ならFallbackResourceのほうが簡潔な場合があります。
この記事ではApache 2.4を前提に、RewriteCondの構文、%{HTTP_HOST}・%{QUERY_STRING}・%{REQUEST_FILENAME}・%{REQUEST_URI}、AND・OR・否定、%1と$1、.htaccessでの違い、安全なredirect例、効かない原因まで解説します。
RewriteCondはRewriteRuleの実行条件
RewriteRuleとRewriteCondの役割は異なります。
| directive | 役割 | 主な対象 |
|---|---|---|
| RewriteRule | matchするpathと書き換え先を決める | URL path |
| RewriteCond | 直後のRewriteRuleを実行する追加条件を決める | host、query、header、filesystemなど |
RewriteCondだけではURLを書き換えません。1件以上のRewriteCondが直後の1件のRewriteRuleへ結び付き、rule patternとすべてのconditionが成立したときにsubstitutionを適用します。
RewriteCond "%{HTTP_HOST}" "^www\.example\.com$" [NC]
RewriteRule "^" "https://example.com%{REQUEST_URI}" [R=302,END]
RewriteRuleのPattern・Substitution・rule flag自体はApache RewriteRuleの書き方で詳しく解説しています。
RewriteCondの基本構文
Apache 2.4の基本構文は次のとおりです。
RewriteCond TestString [!]CondPattern [flags]
| 部分 | 意味 | 例 |
|---|---|---|
| TestString | 検査する値 | %{HTTP_HOST} |
| CondPattern | 正規表現・比較・file test | ^example\.com$、!-f |
| flags | conditionの評価方法 | [NC]、[OR] |
spaceを含むexpressionやflag argumentではquoteが必要です。例の読み間違いを減らすため、この記事では各argumentを原則quoteで囲みます。
否定はCondPatternの先頭に!を付ける
# hostがexample.comではない
RewriteCond "%{HTTP_HOST}" "!^example\.com$" [NC]
!は正規表現だけでなく、!-fや!=onのようなtestにも使えます。否定patternは「matchしなかったとき」にtrueになるため、condition後方参照%1〜%9は作られません。
よく使うTestString変数
RewriteCondのTestStringにはserver・request・HTTP headerなどの変数を展開できます。
| 変数 | 内容 | 主な用途 |
|---|---|---|
%{HTTP_HOST} |
Host request header | canonical host |
%{QUERY_STRING} |
?より後ろ。先頭?は含まない |
query parameter移行 |
%{REQUEST_FILENAME} |
現在requestに対応するfilesystem path | -f・-d |
%{REQUEST_URI} |
requestされたURL path | full pathの条件 |
%{THE_REQUEST} |
clientから届いた元のHTTP request line | internal rewrite前の判定 |
%{HTTPS} |
TLS接続なら通常on |
HTTPS条件 |
%{REQUEST_METHOD} |
GET・POSTなど | method条件 |
%{REMOTE_ADDR} |
Apacheから見えるclient address | 限定的なnetwork条件 |
RewriteRule patternが見るのはURL pathであり、host・port・query stringは含みません。それらを検査するためにRewriteCondを使います。POST bodyなどrequest bodyの内容はmod_rewriteでは検査できないため、application側で処理します。
CondPatternの種類
CondPatternは正規表現だけではありません。文字列比較、数値比較、filesystem test、Apache expressionを使えます。
正規表現
RewriteCond "%{HTTP_HOST}" "^example\.com$" [NC]
^は先頭、$は末尾です。domainのdotは任意の1文字ではなくliteral dotとして扱うため\.にします。
文字列比較
完全一致だけが目的なら=を使えます。
RewriteCond "%{HTTPS}" "!=on"
=、!=のほか、辞書順の<・>・<=・>=があります。数値比較では-eq、-ne、-lt、-le、-gt、-geを使います。
filesystem test
| test | trueになる条件 |
|---|---|
-f |
regular fileが存在する |
-d |
directoryが存在する |
-s |
sizeが0より大きいregular file |
-l・-L・-h |
symbolic link |
-x |
実行権限がある |
-F |
subrequestでaccess可能なfile |
-U |
subrequestでaccess可能なURL |
-F・-Uは内部subrequestを発生させ、performanceへ影響するため、単純な実在確認では通常-f・-dを使います。
複数RewriteCondはAND条件になる
RewriteCondを連続して書くと、defaultでは暗黙のANDです。
RewriteCond "%{REQUEST_FILENAME}" !-f
RewriteCond "%{REQUEST_FILENAME}" !-d
RewriteRule "^" "index.php" [END]
このruleが実行されるのは、次の両方がtrueの場合です。
- request先が実在fileではない
- request先が実在directoryではない
途中のconditionがfalseになれば、そのruleに属する残りのconditionは評価されず、RewriteRuleも実行されません。
OR条件は[OR]を次の条件へつなぐ
[OR]は、そのconditionと「次のcondition」をORでつなぎます。最後のconditionには付けません。
RewriteCond "%{HTTP_HOST}" "^example\.com$" [NC,OR]
RewriteCond "%{HTTP_HOST}" "^www\.example\.com$" [NC]
RewriteRule "^" "https://canonical.example%{REQUEST_URI}" [R=302,END]
これはhostがexample.comまたはwww.example.comのときに実行します。
ORとANDを混ぜるときはまとまりを読む
# (host A OR host B) AND HTTPSではない
RewriteCond "%{HTTP_HOST}" "^example\.com$" [NC,OR]
RewriteCond "%{HTTP_HOST}" "^www\.example\.com$" [NC]
RewriteCond "%{HTTPS}" "!=on"
RewriteRule "^" "https://example.com%{REQUEST_URI}" [R=302,END]
複雑なboolean条件はcopyせず、日本語または論理式へ置き換えてから確認します。条件が増えすぎる場合はRewriteCond exprや<If>も検討します。
RewriteCondで使うflag
RewriteCond固有の主なflagは3種類です。
| flag | 意味 | 注意 |
|---|---|---|
[NC] |
大文字・小文字を区別しない | filesystem・subrequest testには影響しない |
[OR] |
次のconditionとORでつなぐ | short circuitで次を評価しない場合がある |
[NV] |
参照headerをVaryへ追加しない | cacheの正当性を理解した場合だけ使う |
[L]、[END]、[R]、[QSD]などはRewriteCondではなくRewriteRuleのflagです。どちらへ付けるflagかを区別してください。
HTTP_HOSTでdomainを条件にする
特定hostからfixed canonical hostへredirectする例です。検証中は302を使い、確認後に301へ変更します。
RewriteEngine On
RewriteCond "%{HTTP_HOST}" "^www\.example\.com(?::[0-9]+)?$" [NC]
RewriteRule "^" "https://example.com%{REQUEST_URI}" [R=302,END]
local開発でlocalhost:8080のようなport付きHostを扱う場合は、必要な範囲だけ(?::[0-9]+)?で許可します。production canonical hostの設計に不要なportを広く許可しないようにします。
redirect先へHTTP_HOSTをそのまま入れない
Host headerはclientが送る値です。無検証の%{HTTP_HOST}をredirect先へ連結すると、open redirectやHost header injectionにつながる可能性があります。
# 避ける: redirect先hostがrequest header依存
RewriteRule "^" "https://%{HTTP_HOST}%{REQUEST_URI}" [R=302,END]
canonical hostはhttps://example.comのようにfixed valueで書きます。server configを編集できる場合、Apache公式はnon-canonical VirtualHostへRedirectを置く方法をより簡潔な選択肢として案内しています。redirect全体の設計は.htaccessのredirect設定も参照してください。
QUERY_STRINGでparameterを条件にする
%{QUERY_STRING}には先頭の?を含まない文字列が入ります。parameterの途中へ誤matchしないよう、(^|&)と(&|$)で境界を確認します。
# /detail.php?id=123 を /articles/123 へ移行
RewriteCond "%{QUERY_STRING}" "(^|&)id=([0-9]+)(&|$)"
RewriteRule "^detail\.php$" "/articles/%2" [R=302,END,QSD]
このconditionでは第2 capture groupがIDなので、RewriteRuleのsubstitutionで%2を使います。[QSD]は元のquery stringを捨てるflagです。
QSDとQSAを使い分ける
[QSD]: substitutionにqueryがない場合、元のqueryを破棄する[QSA]: substitutionが作るqueryへ元のqueryを追加する
# /products/123?ref=mail
# → /product.php?id=123&ref=mail
RewriteRule "^products/([0-9]+)$" "/product.php?id=$1" [END,QSA]
古い設定ではsubstitution末尾の?でqueryを捨てる例がありますが、Apache 2.4では意図が明確な[QSD]を使えます。[QSD]と[QSA]を同時に指定するとQSDが優先されます。
REQUEST_FILENAMEでfile・directoryを判定する
%{REQUEST_FILENAME}は現在requestに対応するfilesystem pathとして使い、-f・-dと組み合わせます。
fileが存在する場合
RewriteCond "%{REQUEST_FILENAME}" -f
RewriteRule "^downloads/" "-" [END]
substitutionの-はpathを書き換えない指定です。
file・directoryが存在しない場合
RewriteEngine On
RewriteCond "%{REQUEST_FILENAME}" !-f
RewriteCond "%{REQUEST_FILENAME}" !-d
RewriteRule "^" "index.php" [END]
static file・directoryをApacheが通常処理し、それ以外をapplicationへ渡すfront controller patternです。.htaccessでは[END]を使うと、そのrequestに対する以後のper-directory rewrite処理も終了でき、internal redirectによる再処理loopを避けやすくなります。
単純なfront controllerならFallbackResourceを比較する
追加条件が不要なら、Apache公式はFallbackResourceをより簡潔な選択肢として案内しています。
FallbackResource /index.php
「存在しないresourceを1つのentry pointへ渡す」だけならまず比較し、host・headerなど複雑な条件が必要な場合にmod_rewriteを使います。
REQUEST_URIでfull URL pathを条件にする
.htaccessのRewriteRule patternはdirectory prefixが除かれ、先頭slashもありません。対して%{REQUEST_URI}ではrequestのfull URL pathを条件にできます。
# DocumentRootの.htaccess
RewriteCond "%{REQUEST_URI}" "^/admin/" [NC]
RewriteRule "^" "-" [F,END]
query stringはREQUEST_URIではなくQUERY_STRINGで別に検査します。URLのpathとfilesystem pathも混同せず、URL条件はREQUEST_URI、実在確認はREQUEST_FILENAMEを基準にします。
THE_REQUESTでclientの元requestを確認する
%{THE_REQUEST}は、clientから届いた元のrequest lineです。internal rewrite後も変化しないため、「clientがindex.phpを直接requestした場合だけ」redirectするといった判定に使えます。
# GET /index.php HTTP/1.1 の直接requestだけ / へ
RewriteCond "%{THE_REQUEST}" "\s/+index\.php(?:[?\s])" [NC]
RewriteRule "^index\.php$" "/" [R=302,END,QSD]
application内部で/をindex.phpへrewriteしたあと、index.phpから/へ戻すloopを避ける用途があります。THE_REQUESTはraw request lineなので、通常のURL path判定にはREQUEST_URIを使い、必要な場面だけ選びます。
HTTPSを条件にする
ApacheがclientとのTLS接続を直接終端している構成では、%{HTTPS}を使えます。
RewriteCond "%{HTTPS}" "!=on"
RewriteRule "^" "https://example.com%{REQUEST_URI}" [R=302,END]
redirect先はfixed canonical hostにします。query stringはsubstitutionに新しいqueryを指定していないため、defaultでは引き継がれます。
reverse proxy・CDN配下ではredirect loopに注意する
TLSをCDNやload balancerで終端し、ApacheへHTTPでforwardする構成では、browser側がHTTPSでもApacheの%{HTTPS}がoffになる場合があります。そのままredirectするとloopすることがあります。
この場合は次の順で設計します。
- edge proxy・CDN側でcanonical HTTPS redirectを行えるか確認する
- Apacheがtrusted proxy情報を正しく認識する構成をserver管理者と決める
- 外部clientが偽装できるforwarded headerを無条件に信用しない
環境を確認せずX-Forwarded-Protoだけをcopyした条件は追加しないでください。
RewriteCondの後方参照は%1、RewriteRuleは$1
captureした値のsourceによって記号が違います。
| 記法 | source | 例 |
|---|---|---|
$1〜$9 |
RewriteRule Patternのcapture group | path segment |
%1〜%9 |
最後にmatchしたRewriteCond CondPatternのcapture group | host・query value |
RewriteCond "%{HTTP_HOST}" "^([a-z0-9-]+)\.example\.com$" [NC]
RewriteRule "^products/([0-9]+)$" "/tenant.php?name=%1&id=$1" [END]
%1: RewriteCondがcaptureしたsubdomain$1: RewriteRuleがcaptureしたproduct ID
複数RewriteCondがある場合、%Nは最後にmatchしたCondPatternに由来します。後ろへ別conditionを追加するとsourceが変わることがあるため、captureの位置を確認してください。否定conditionからは後方参照を取得できません。
RewriteRule patternはconditionより先に評価される
設定上はRewriteCondが上に書かれていますが、Apacheは対象RewriteRuleのpatternを先に評価するため、そのruleの$1をpreceding RewriteCondのTestStringで利用できます。初心者には読みにくいため、必要がなければ複雑な相互参照を避けます。
.htaccessとserver configの違い
同じRewriteRuleでもcontextでmatch対象が変わります。
| context | RewriteRuleが見るpath | 先頭slash |
|---|---|---|
| server・VirtualHost | URL path | 含む |
| .htaccess・Directory | directory prefixを除いたrelative path | 含まない |
DocumentRootの.htaccessでは次のように先頭slashなしで書きます。
RewriteRule "^old-page$" "/new-page" [R=302,END]
server・VirtualHostとの両方へ移しやすいpatternにする場合、^/?old-page$のようにslashをoptionalにする例もあります。
.htaccessではAllowOverrideが必要
server側がAllowOverride Noneなら.htaccessは読み込まれません。mod_rewrite directiveにはFileInfo override classが必要です。配置・AllowOverride・優先順位はApache .htaccessの書き方を参照してください。
[L]と[END]の違い
server contextの[L]は現在のrulesetを終了します。.htaccessではcurrent passだけを終了し、substitutionによるinternal redirectでrulesetが先頭から再実行される場合があります。
Apache 2.4の[END]は、そのrequestに対する以後のper-directory rewrite処理も終了します。.htaccessで最終的なinternal rewriteを行う場合はloop防止に有効です。ただし、後続directoryで必要なruleまで止めないか構成全体を確認します。
RewriteCond exprで複雑な条件を書く
TestStringへexprを指定すると、Apache 2.4のap_exprを使えます。
RewriteCond expr "%{REQUEST_METHOD} == 'GET' && %{REQUEST_URI} =~ m#^/preview/#"
RewriteRule "^" "-" [F,END]
AND・OR・比較を1つのexpressionとして明示できます。ただしaccess controlならRequire、一般的なconditional configなら<If>のほうが意図を表しやすい場合があります。mod_rewriteへすべての条件分岐を集めないことも重要です。
mod_rewriteを使わないほうがよい場合
Apache公式は、目的に合う単純なdirectiveがある場合はそちらを案内しています。
| 目的 | 先に比較する方法 |
|---|---|
| 固定URLのredirect | Redirect・RedirectMatch |
| non-canonical hostの全redirect | 専用VirtualHostのRedirect |
| 存在しないURLをentry pointへ渡す | FallbackResource |
| access control | Require |
| 一般的な条件付き設定 | <If>とap_expr |
mod_rewriteは複雑なURL manipulationに強力ですが、短い専用directiveのほうが保守しやすく、誤設定も減らせます。
安全に設定を検証する手順
productionでいきなり301を有効にせず、次の順で確認します。
- 現在の設定とrollback方法を保存する
- local・stagingで302を使って試す
apachectl configtestまたはhttpd -tでsyntaxを確認するcurl -IでstatusとLocationを確認する- query、host、存在file、存在しないpathなどtrue / false両方をtestする
- error log・rewrite traceで実際の評価を確認する
- loop・asset 404・method・cacheを確認後、必要なら301へ変更する
apachectl configtest
curl -I 'http://www.example.com/old-page?id=123'
301はbrowserやintermediaryへcacheされるため、検証中は302を使います。Apache設定全体を変更した場合はsyntax確認後にgraceful reloadし、.htaccess変更は通常requestごとに読み込まれます。
rewrite traceを一時的に有効にする
server config・VirtualHostで一時的にlog levelを上げると、conditionとruleの評価を追跡できます。
LogLevel warn rewrite:trace3
高いtrace levelは大量のlogと負荷を生むため、必要な範囲・期間だけ使い、確認後に戻します。logの場所と確認方法はApache rewrite logの出力方法とApache error.log・access.logの確認方法で扱っています。
RewriteCondが効かない原因
RewriteEngine Onがない
mod_rewriteのruntime engineはdefault offです。適用contextでRewriteEngine Onを設定します。module自体が有効かも確認します。
.htaccessが読み込まれていない
AllowOverrideがNone、FileInfoが許可されていない、DocumentRootが想定と違う、別の.htaccessが適用されている可能性を確認します。
RewriteRuleへ先頭slashを書いている
.htaccessと<Directory>のRewriteRule patternには先頭slashが入りません。full pathをconditionにするならREQUEST_URIを使います。
QUERY_STRINGをRewriteRuleだけでmatchしている
RewriteRule Patternはquery stringを含みません。QUERY_STRINGをRewriteCondで検査し、parameter boundaryも確認します。
%1と$1を取り違えている
RewriteCondのcaptureは%N、RewriteRuleのcaptureは$Nです。別のRewriteCondを後ろに追加して%Nのsourceが変わっていないかも確認します。
REQUEST_FILENAMEが想定pathではない
Alias、proxy、rewrite phase、既に実行されたruleによってmappingが変わる場合があります。trace logでTestStringの実値を確認し、URL pathとfilesystem pathを混同していないか調べます。
[L]で完全に終了したと思っている
.htaccessではinternal redirect後に再処理されることがあります。500とAH00124が出る場合はloop条件を確認し、END、実在file除外、THE_REQUESTなどを用途に合わせて使います。
HTTPS redirectがloopする
reverse proxy・CDNがTLSを終端していると、Apacheから見えるschemeとbrowser側が異なる場合があります。edgeのredirect設定とtrusted proxy構成を確認します。
以前の301がcacheされている
curl、private window、別profileでresponseを確認します。ruleの修正だけではclient側に残った301が消えない場合があります。
MultiViewsが先にresourceを解決している
content negotiationのMultiViewsが有効だと、extensionなしpathが別fileへ解決され、想定したrewriteにならないことがあります。使用していないなら対象directoryでOptions -MultiViewsを検討し、ほかの機能への影響も確認します。
RewriteCondのsecurity注意点
RewriteCondでuser inputを扱うときは、matchするだけでなくsubstitutionの行き先も確認します。
open redirect
HTTP_HOST、QUERY_STRING、path captureをabsolute redirect先のhostへ無検証で使わないでください。destination hostはfixed valueにし、pathやIDも許可文字へ絞ります。
SSRF
[P]でproxyするURLへuser inputを入れると、内部serviceや外部hostへのrequestを攻撃者が操作できる可能性があります。proxy target hostはfixedにし、backreferenceは必要なpathだけへ制限します。
path traversal
.+でcaptureしたpathをfilesystemへ直接連結せず、[a-zA-Z0-9_-]+のような許可pattern、Directory access control、application validationを組み合わせます。
encoded characterとbackreference
mod_rewriteはURL pathをdecodeしてpatternへmatchさせます。captureをqueryやpathへ再利用する場合、RewriteRuleの[B]など適切な再escapeが必要になることがあります。文字列の意味を理解せず[NE]でescapeを無効にしないでください。
RewriteCondチェックリスト
- RewriteCondが直後のどのRewriteRuleへ属するか確認した
- TestString・CondPattern・flagsを分けて読んだ
- 複数conditionのAND・ORのまとまりを確認した
- 否定conditionから後方参照を取得していない
%Nと$Nを区別した- HTTP_HOST・QUERY_STRING・REQUEST_URI・REQUEST_FILENAMEを使い分けた
- .htaccessのRewriteRule patternへ先頭slashを付けていない
- .htaccessの再処理loopとENDの影響を確認した
- queryを残す・追加する・捨てる動作を決めた
- redirect先hostをfixed valueにした
- proxy配下のHTTPS判定を実構成で確認した
- Redirect・FallbackResource・Require・Ifで簡潔にできないか比較した
- 302とconfigtest・curl・trace logでtrue / false両方をtestした
- user inputによるopen redirect・SSRF・path traversalを防いだ
まとめ
RewriteCondは、host、query string、filesystem、request methodなどを条件にして、直後のRewriteRuleを実行するdirectiveです。複数conditionはdefaultでAND、[OR]は次のconditionとのOR、否定はCondPattern先頭の!で表します。
検索でよく使う%{HTTP_HOST}、%{QUERY_STRING}、%{REQUEST_FILENAME}は、それぞれhost header、query、filesystem pathという異なる値です。.htaccessでは先頭slashと再処理loopにも注意してください。
設定は固定host、限定したpattern、temporary 302から始め、configtest、curl、rewrite traceで確認します。単純なredirectやfront controllerなら、RedirectやFallbackResourceのほうが読みやすく安全な場合もあります。