mod_rewrite
を主題に体系化したものです。( )で囲んだ部分を後で使い回す仕組み(バックリファレンス)を、%1 / $1 の違い、非キャプチャ、名前付きキャプチャ、欲張り/最短マッチ、検証方法まで含めて解説します。
用語の整理(まずここだけ読めばOK)
- キャプチャ(捕捉グループ):正規表現で
( ... )
と丸カッコで囲んだ部分。マッチした文字列を保存し、後で参照できる。 - バックリファレンス:保存した値を参照する記法。
$1, $2, ...
:RewriteRule
のパターン側で作ったキャプチャを参照。%1, %2, ...
:直前のRewriteCond
(条件側)で作ったキャプチャを参照。
※
$
と%
で参照元が違う点が最重要。 - 非キャプチャグループ:
(?: ... )
。グループ化はするが保存しない(番号を消費しない)。 - 名前付きキャプチャ:
(?<name> ... )
(PCRE)。Apacheの置換部では名前参照は不可で番号で参照する(PHP/JSでは名前で参照可)。 - 欲張り/最短:
+
,*
は欲張り(できるだけ長く)。+?
,*?
は最短。 - アンカー:
^
(先頭)、$
(末尾)。範囲を固定し、過剰マッチを防ぐ。 - 判定と実行:
RewriteCond
が判定、RewriteRule
が実行。複数の条件は AND で評価され、すべて真なら直後の1本のRewriteRule
が実行される。
mod_rewriteにおけるキャプチャの基本構文
<IfModule mod_rewrite.c>
RewriteEngine On
# (例)判定:ホスト名が "www." で始まるか?
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
# 実行:%1(条件側で捕捉した非www部分)と同じパスへ301
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]
</IfModule>
- どこがキャプチャ?
(.+)
がキャプチャ。www.
の後ろ全部を保存し、%1
で参照。 - なぜ %1? 直前の
RewriteCond
のキャプチャ参照は %1。RewriteRule
のキャプチャ参照は $1。 - 注意:
^www\.
のドットは正規表現では「任意1文字」なので\.
とエスケープしないと誤マッチする。
「」と「%1」の違いを完全理解
# 例:サブドメインを %1 で取り出し、パスの数値IDを $1 で取り出す
RewriteEngine On
# 判定(条件側のキャプチャ):sub.example.com → %1 = "sub"
RewriteCond %{HTTP_HOST} ^([^.]+)\.example\.com$ [NC]
# 実行(ルール側のキャプチャ):/post/123 → $1 = "123"
RewriteRule ^post/([0-9]+)/?$ /app.php?sub=%1&id=$1 [QSA,L]
- 条件側:
([^.]+)
が%1
。 - ルール側:
([0-9]+)
が$1
。 - 活用:条件でドメインや言語、ルールでパス断片を拾い、置換先で組み立て直すのが定石。
非キャプチャグループ (?: ... ) の使いどころ
# 例:"index.php/" の有無を許可して最短にする(番号を消費しない)
RewriteRule ^(?:index\.php/)?(.*)$ /index.php?path=$1 [QSA,L]
- メリット:番号がズレないので、
$1
の意味が変わらない。読みやすさと保守性が上がる。 - 最短化:
?
は直前の要素を「0回または1回」。(?: ... )?
は「グループ全体が任意」。
名前付きキャプチャ(概念と注意)
# PCREの例(概念理解用)— 名前付きキャプチャ
(?<id>[0-9]+) # "id" という名前で捕捉
# ただし mod_rewrite では置換部で "名前" 参照はできない(番号で参照する)
- Apacheの置換では番号参照のみ:
$1
/%1
等。名前はPHPやJSなどの言語側で有効。 - 実務Tip:mod_rewrite では「番号+コメント」で明示する(例:
# $1 = postId
)。
欲張り(Greedy)と最短(Lazy)で意味が変わる
# Greedy(できるだけ長く)
^(.+)/(.+)$
# Lazy(できるだけ短く)
^(.+?)/(.+)$
- 違い:Greedy は後ろのグループが空になりやすい。Lazy は手前を最短で確定しやすい。
- 実務:区切り文字(
/
や.
)の前で止めたい時は Lazy を検討。
アンカー(^/$)で範囲を固定する
# 末尾スラッシュ無しページのみを捕捉
^([A-Za-z0-9/_-]+)$
- アンカー必須:
^
と$
を付けないと、意図しない位置でもマッチしがち。 - エスケープ:
.
や?
は意味を持つので\.
や\?
に。
キャプチャ活用レシピ(行コメントで判定と実行を明示)
1) www除去:%1 の基本パターン
RewriteEngine On
# 判定:^www\.(.+)$ — www. の後ろ全部を (.+) で捕捉 → %1
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
# 実行:%1(非wwwホスト) + 同じパスへ 301
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]
2) 言語サブディレクトリを捕捉
# /ja/xxx → /index.php?lang=ja&path=xxx
RewriteRule ^(ja|en|fr)/(.*)$ /index.php?lang=$1&path=$2 [QSA,L]
- $1 は言語コード、$2 は残りのパス。
- 固定候補は
(?: ... | ... )
で列挙。必要なら[A-Za-z]{2}
など汎用化。
3) IDの抽出(数値のみ)
# /post/123/ → /post.php?id=123
RewriteRule ^post/([0-9]+)/?$ /post.php?id=$1 [QSA,L]
桁数制限は {1,6}
などで与えると誤マッチが減る。
4) クエリ文字列から値を捕捉(条件側)
# 例:?ref=twitter を検知し内部でマーク
# 判定:QUERY_STRING に ref=... が含まれる → %1 = "twitter"
RewriteCond %{QUERY_STRING} (^|&)ref=([^&]+)(&|$)
# 実行:環境変数をセット(のちほどヘッダ等で可視化)
RewriteRule ^ - [E=REF:%2,L]
- ポイント:
%2
は条件側キャプチャの2番目(ref
の値)。 - クエリを正規化(不要パラメータを捨てる)するなら
[QSD]
を使う。
キャプチャが効いているかの確認手順
1) リダイレクトはヘッダで確認
curl -I http://example.com/post/123
# → HTTP/1.1 301 Moved Permanently
# Location: https://example.com/post.php?id=123
2) 内部リライトは環境変数+一時ヘッダ
# 実行時に環境変数 HIT=1 を付与(ルール側での例)
RewriteRule ^post/([0-9]+)/?$ /post.php?id=$1 [E=HIT:1,L]
# ヘッダ出力(VHost/Directoryで)
Header set X-Rewrite "1" env=HIT
レスポンスに X-Rewrite: 1
が出れば、$1
を使うルールが発動したと分かる。調査後は削除。
3) 調査中のみ詳細ログ
LogLevel rewrite:trace2 # 終わったら warn に戻す
落とし穴とベストプラクティス
- 「必要な所だけ」キャプチャ:グループ化だけなら
(?: ... )
を使い、番号ズレを防ぐ。 - 順序の影響:途中に
( ... )
を追加すると$2 → $3
のように番号がズレる。既存置換部の見直し必須。 - アンカー徹底:
^
/$
を使い、過剰マッチや意図しない部分一致を抑える。 - ドットはエスケープ:
.
は「任意1文字」。ドメインや拡張子では\.
に。 - MultiViews干渉:Apacheの
Options -MultiViews
を検討。拡張子隠し系と競合しやすい。 - 名前付きキャプチャの過信NG:mod_rewriteの置換では番号参照のみ。コメントで意味を残す。
実装チェックリスト
- [ ] 何を捕捉するかが明確(要件→正規表現→テストケース)
- [ ] アンカーとエスケープを適切に使用
- [ ] 不要箇所は
(?: ... )
(非キャプチャ)で番号保全 - [ ]
%1
/$1
の参照元をコメントで明示 - [ ]
curl -I
と一時ヘッダ/ログで実機検証
本記事は Apache mod_rewrite と PCRE を前提に記述しています。CDN/プロキシ配下では HTTPS 判定やクライアントIP取得方法が変わるため、前段のヘッダ(X-Forwarded-*)設計も合わせて確認してください。
コメント