Apache

Apache RewriteCondの書き方・使い方|条件式・変数・AND/ORを実例解説

Apache RewriteCondの書き方を、HTTP_HOST・QUERY_STRING・REQUEST_FILENAME・REQUEST_URIの実例で解説します。AND/OR、否定、後方参照、QSD、.htaccessの違い、効かない原因と安全な検証方法まで整理します。

この記事の目次
  1. RewriteCondはRewriteRuleの実行条件
  2. RewriteCondの基本構文
  3. 否定はCondPatternの先頭に!を付ける
  4. よく使うTestString変数
  5. CondPatternの種類
  6. 正規表現
  7. 文字列比較
  8. filesystem test
  9. 複数RewriteCondはAND条件になる
  10. OR条件は[OR]を次の条件へつなぐ
  11. ORとANDを混ぜるときはまとまりを読む
  12. RewriteCondで使うflag
  13. HTTP_HOSTでdomainを条件にする
  14. redirect先へHTTP_HOSTをそのまま入れない
  15. QUERY_STRINGでparameterを条件にする
  16. QSDとQSAを使い分ける
  17. REQUEST_FILENAMEでfile・directoryを判定する
  18. fileが存在する場合
  19. file・directoryが存在しない場合
  20. 単純なfront controllerならFallbackResourceを比較する
  21. REQUEST_URIでfull URL pathを条件にする
  22. THE_REQUESTでclientの元requestを確認する
  23. HTTPSを条件にする
  24. reverse proxy・CDN配下ではredirect loopに注意する
  25. RewriteCondの後方参照は%1、RewriteRuleは$1
  26. RewriteRule patternはconditionより先に評価される
  27. .htaccessとserver configの違い
  28. .htaccessではAllowOverrideが必要
  29. [L]と[END]の違い
  30. RewriteCond exprで複雑な条件を書く
  31. mod_rewriteを使わないほうがよい場合
  32. 安全に設定を検証する手順
  33. rewrite traceを一時的に有効にする
  34. RewriteCondが効かない原因
  35. RewriteEngine Onがない
  36. .htaccessが読み込まれていない
  37. RewriteRuleへ先頭slashを書いている
  38. QUERY_STRINGをRewriteRuleだけでmatchしている
  39. %1と$1を取り違えている
  40. REQUEST_FILENAMEが想定pathではない
  41. [L]で完全に終了したと思っている
  42. HTTPS redirectがloopする
  43. 以前の301がcacheされている
  44. MultiViewsが先にresourceを解決している
  45. RewriteCondのsecurity注意点
  46. open redirect
  47. SSRF
  48. path traversal
  49. encoded characterとbackreference
  50. RewriteCondチェックリスト
  51. まとめ

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することがあります。

この場合は次の順で設計します。

  1. edge proxy・CDN側でcanonical HTTPS redirectを行えるか確認する
  2. Apacheがtrusted proxy情報を正しく認識する構成をserver管理者と決める
  3. 外部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 RedirectRedirectMatch
non-canonical hostの全redirect 専用VirtualHostのRedirect
存在しないURLをentry pointへ渡す FallbackResource
access control Require
一般的な条件付き設定 <If>とap_expr

mod_rewriteは複雑なURL manipulationに強力ですが、短い専用directiveのほうが保守しやすく、誤設定も減らせます。

安全に設定を検証する手順

productionでいきなり301を有効にせず、次の順で確認します。

  1. 現在の設定とrollback方法を保存する
  2. local・stagingで302を使って試す
  3. apachectl configtestまたはhttpd -tでsyntaxを確認する
  4. curl -IでstatusとLocationを確認する
  5. query、host、存在file、存在しないpathなどtrue / false両方をtestする
  6. error log・rewrite traceで実際の評価を確認する
  7. 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のほうが読みやすく安全な場合もあります。

スポンサーリンク