2026年6月3日
2026年6月3日
mod_rewriteのRewriteCondとRewriteRuleの書き方(構文・後方参照・フラグと実例)
mod_rewrite は、Apache が受け取った URL を条件に応じて書き換えたり、別の URL へリダイレクトしたりするモジュールです。中心になるのは2つのディレクティブで、RewriteRule が「どの URL をどう書き換えるか」、RewriteCond が「その書き換えをどんな条件のときに行うか」を決めます。
この記事では、構文と動きの規則を押さえたうえで、よく使う書き方を実例で説明します。例はすべて Apache 2.4(2026年11月時点の httpd 2.4 公式イメージ)で curl を使って結果を確かめています。
使う前の準備
Debian/Ubuntu では mod_rewrite を有効にして再起動します。
sudo a2enmod rewrite
sudo systemctl restart apache2
.htaccess に書く場合は、そのディレクトリで .htaccess の設定が許可されている必要があります。サーバー設定の <Directory> に次のような指定があるか確認してください。AllowOverride に FileInfo(または All)が含まれていないと .htaccess の Rewrite 系ディレクティブは使えず、Options に FollowSymLinks か SymLinksIfOwnerMatch が無いとリクエストは 403 になります。
<Directory /var/www/html>
Options FollowSymLinks
AllowOverride FileInfo
Require all granted
</Directory>
どちらの場所に書く場合も、最初に RewriteEngine On が必要です。
RewriteRule の構文
RewriteRule パターン 置換先 [フラグ]
- パターン: URL のパス部分に対する正規表現(PCRE)です。ホスト名やクエリ文字列(
?以降)は含まれません。 - 置換先: 書き換え後のパス、または
https://から始まる URL です。-を書くと書き換えずにフラグだけを適用します。 - フラグ:
[R=301,L]のように角かっこの中にカンマ区切りで書きます。
.htaccess とサーバー設定でパターンが変わる
つまずきやすいのがここです。<VirtualHost> などのサーバー設定に書いた場合、パターンが照合する文字列は /oldpage.html のように先頭のスラッシュから始まります。一方、.htaccess(と <Directory> の中)では、そのディレクトリまでのパスが取り除かれ、oldpage.html のように先頭のスラッシュが付きません。
# サーバー設定(VirtualHost)に書く場合
RewriteRule ^/oldpage\.html$ /newpage.html [R=301,L]
# .htaccess に書く場合
RewriteRule ^oldpage\.html$ /newpage.html [R=301,L]
.htaccess に ^/oldpage\.html$ と書くと一致せず、何も起きません(実際に試すと 404 のままでした)。サーバー設定から .htaccess へ、またはその逆へ移すときは、パターンの先頭を見直してください。
RewriteCond の構文
RewriteCond テスト文字列 条件パターン [フラグ]
RewriteCond は、直後の1つの RewriteRule にだけかかります。複数の RewriteCond を並べると、すべてが成り立ったとき(AND)にだけ、そのすぐ下の RewriteRule が実行されます。2つ先のルールには効かないので、条件が必要なルールごとに書き直します。
テスト文字列には、%{変数名} の形でサーバー変数を使います。よく使うものは次のとおりです。
| 変数 | 中身の例 |
|---|---|
%{HTTP_HOST} |
リクエストのホスト名(example.com) |
%{HTTPS} |
HTTPS なら on、そうでなければ off |
%{REQUEST_URI} |
パス(/blog/index.php)。クエリ文字列は含まない |
%{QUERY_STRING} |
? 以降(id=15&cat=2) |
%{REQUEST_FILENAME} |
対応するファイルシステム上のパス |
%{REMOTE_ADDR} |
接続元の IP アドレス |
%{HTTP:ヘッダー名} |
任意のリクエストヘッダー(%{HTTP:X-Forwarded-Proto} など) |
条件パターンには正規表現のほか、次の特別な指定が使えます。先頭に ! を付けると否定になります。
| 指定 | 意味 |
|---|---|
-f |
通常のファイルとして存在する |
-d |
ディレクトリとして存在する |
-s |
サイズが 0 より大きいファイルとして存在する |
-l |
シンボリックリンクとして存在する |
RewriteCond のフラグは主に2つです。[NC] は大文字小文字を区別しない、[OR] は次の条件との関係を OR にします。
後方参照: $1 と %1
正規表現のかっこで取り出した部分は、置換先で使えます。
$1〜$9:RewriteRuleのパターンで取り出した部分%1〜%9: 最後に一致したRewriteCondの条件パターンで取り出した部分
# /blog/2024/05/hello-world → /articles/hello-world
RewriteRule ^blog/(\d{4})/(\d{2})/(.+)$ /articles/$3 [R=301,L]
よく使うフラグ
| フラグ | 意味 |
|---|---|
L |
このルールで処理を止める(後述の注意あり) |
R=301 / R=302 |
外部リダイレクトする。R だけなら 302 |
NC |
大文字小文字を区別しない |
QSA |
元のクエリ文字列を、置換先のクエリ文字列に追加する |
QSD |
元のクエリ文字列を捨てる(Apache 2.4 以降) |
F |
403 Forbidden を返す |
G |
410 Gone を返す |
END |
書き換えをここで終え、.htaccess の再処理も行わない(Apache 2.4 以降) |
NE |
置換先の # や % などをエスケープしない |
.htaccess では、L で止まったあとも、書き換えた URL でもう一度 .htaccess の処理が最初から行われます。書き換え先が再び同じルールに一致すると、ループや意図しない二重の書き換えが起きます。書き換えをそこで終わらせたい場合は END を使います。
実例
次の例は、.htaccess にこの順で並べて動作を確かめたものです。リダイレクトのルールを先に、内部の書き換え(フロントコントローラー)を最後に置くのが基本です。
RewriteEngine On
# 1) example.com を www.example.com にそろえる
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^(.*)$ https://www.example.com/$1 [R=301,L]
# 2) HTTP を HTTPS にリダイレクトする
RewriteCond %{HTTPS} off
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
# 3) 旧ページを新ページへ転送する
RewriteRule ^oldpage\.html$ https://www.example.com/newpage.html [R=301,L]
# 4) クエリ文字列の値を使って転送し、元のクエリは捨てる
RewriteCond %{QUERY_STRING} (?:^|&)id=([0-9]+)(?:&|$)
RewriteRule ^product\.php$ https://www.example.com/products/%1/ [R=301,L,QSD]
# 5) 実在しないパスをすべて index.php で処理する
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [L]
確かめた結果は次のとおりです。
| リクエスト | 結果 |
|---|---|
http://example.com/foo/bar?x=1 |
301 → https://www.example.com/foo/bar?x=1 |
http://www.example.com/foo |
301 → https://www.example.com/foo |
/oldpage.html(HTTPS) |
301 → https://www.example.com/newpage.html |
/product.php?cat=2&id=15 |
301 → https://www.example.com/products/15/(クエリなし) |
/product.php?cat=2 |
書き換えなし(id が無いので条件に一致しない) |
/some/pretty/url |
200(index.php が応答) |
/style.css(実在するファイル) |
200(ファイルをそのまま返す) |
それぞれのポイントです。
- 1)
RewriteRuleのパターンにはクエリ文字列が含まれませんが、置換先にクエリを書いていない場合、元のクエリ文字列は自動で引き継がれます(?x=1が残っています)。 - 2) ロードバランサーや CDN で HTTPS を終端している場合、Apache から見ると
%{HTTPS}は常にoffです。X-Forwarded-Protoの条件を加えないと、リダイレクトが無限に繰り返されます。プロキシを使っていなければ、2行目の条件は不要です。 - 3) 置換先を
/newpage.htmlのようなパスだけにすると、リダイレクト先のhttp/httpsは Apache が受けた接続に合わせて決まります。HTTPS をプロキシで終端している環境ではhttp://へ転送されてしまうことがあるため、リダイレクトではhttps://から始まる URL を書いておくと迷いません。 - 4) クエリ文字列は
RewriteRuleのパターンでは照合できないので、%{QUERY_STRING}をRewriteCondで調べ、取り出した値を%1で使います。QSDが無いと、元の?cat=2&id=15が転送先に付いたままになります。 - 5) WordPress などの CMS と同じ形です。実在するファイルやディレクトリは除外し、それ以外を
index.phpに渡します。
条件を OR でつなぐ例
管理画面と特定のパスへのアクセスを、決まった IP アドレス以外は 403 にする例です。最初の2つの条件は [OR] でつながり、その結果と3つ目の条件が AND になります。
RewriteCond %{REQUEST_URI} ^/admin [OR]
RewriteCond %{REQUEST_URI} ^/secret
RewriteCond %{REMOTE_ADDR} !^192\.0\.2\.
RewriteRule ^ - [F]
%{REQUEST_URI} は .htaccess でも先頭のスラッシュを含むので、この例はサーバー設定と .htaccess のどちらでも同じように書けます。
mod_rewrite を使わなくてよい場面
単純な転送だけなら、mod_alias の Redirect や RedirectMatch のほうが読みやすく、間違いも起きにくくなります。Apache の公式ドキュメントも、ほかの方法で済む場合は mod_rewrite を使わないよう勧めています。
Redirect 301 /oldpage.html https://www.example.com/newpage.html
RedirectMatch 301 ^/blog/(\d{4})/(\d{2})/(.+)$ https://www.example.com/articles/$3
条件(ホスト名、クエリ文字列、ファイルの有無など)で分けたいときが mod_rewrite の出番です。
動かないときの確かめ方
リダイレクトは curl で確かめる
ブラウザは 301 を記憶するため、ルールを直しても古い転送先へ飛び続けることがあります。curl -I https://www.example.com/oldpage.html で Location ヘッダーを見るか、試している間は R=302 にしておき、確定してから R=301 に変えます。
書き換えのログを出す
Apache 2.4 では RewriteLog は廃止され、LogLevel で出力します。サーバー設定(.htaccess ではなく VirtualHost など)に書き、エラーログを見ます。
LogLevel warn rewrite:trace3
sudo tail -f /var/log/apache2/error.log | grep rewrite
ログには、照合した文字列(.htaccess では先頭のスラッシュが取れたパス)、各条件の結果、書き換え後の値が出ます。trace レベルのログは量が多く処理も重くなるので、確認が終わったら元に戻してください。詳しい読み方はApacheのmod_rewriteをデバッグログで調べる方法でも説明しています。
設定の構文を確かめる
サーバー設定を変えたときは sudo apache2ctl configtest で確認してから再読み込みします。.htaccess の誤りは、アクセスしたときに 500 エラーとしてエラーログに出ます。
まとめ
RewriteRule パターン 置換先 [フラグ]で書き換え、RewriteCondで直後の1つのルールに条件を付けます- パターンはパスだけに照合し、
.htaccessでは先頭のスラッシュが付きません $1はルールのパターン、%1は直前に一致した条件から取り出した値です- クエリ文字列は
%{QUERY_STRING}で調べ、不要ならQSDで捨てます - リダイレクトを先に、フロントコントローラーのような内部の書き換えを最後に置きます
- 単純な転送は
Redirectで足り、確かめるときは curl とLogLevel rewrite:trace3を使います