2026年5月26日
2026年5月26日
Apacheのmod_rewriteでURLリダイレクトする方法|サイト移転・URL変更時の一括転送の組み立て方
サイト移転やパーマリンク変更で大量の URL を転送するときは、「規則で書けるものは正規表現の1行に、規則にならないものは対応表(RewriteMap)に」と分けて組み立てると、ルールが膨らまず保守もしやすくなります。この記事では、mod_rewrite による一括リダイレクトを、旧URLの洗い出しからフラグの使い分け、curl での検証、公開後の確認まで順に説明します。
http から https への統一や www の有無の統一といった定番の1行ルールは別の記事で扱っているため、ここでは「何百本もの URL を新しい構造へ移す」場面に絞ります。
作業の前に旧URLの一覧を作る
一括転送で一番多い失敗は、ルールの書き方ではなく転送漏れです。ルールを書く前に、転送すべき旧URLを次のような情報源から集めます。
- 移転前サイトの XML サイトマップ(WordPress なら標準の
wp-sitemap.xmlか SEO プラグインのもの) - Google Search Console の「ページ」レポートや検索パフォーマンスに出ている URL
- アクセスログで実際にリクエストされている URL(外部サイトからのリンク経由のものも拾えます)
集めた URL を表計算ソフトに並べ、新URLを対応させます。並べてみると、「年月が付いたパーマリンクから投稿名だけへ」のように規則で変換できるグループと、記事の統合や名前の変更のように1件ずつ対応を決めるしかないグループに分かれるはずです。前者は正規表現、後者は対応表で処理します。対応する新URLがないページは、無理に無関係なページやトップページへ転送せず、404 や 410 を返すほうが利用者にも検索エンジンにも分かりやすい結果になります。
作業は本番に入れる前にステージング環境で試し、現在の .htaccess やバーチャルホストの設定ファイルはバックアップしておいてください。
規則で変換できるURLは正規表現で書く
WordPress でよくある構造変更を例にします。以下は WordPress の .htaccess に書く場合の例で、# BEGIN WordPress のブロックより上に置きます。WordPress のブロックは存在しないファイルへのリクエストをすべて index.php に渡すため、下に書くと転送ルールが評価される前に WordPress の処理へ流れてしまいます。
<IfModule mod_rewrite.c>
RewriteEngine On
# 例1: ドメイン移転(パスはそのまま新ドメインへ)
RewriteCond %{HTTP_HOST} ^(www\.)?old-example\.com$ [NC]
RewriteRule ^(.*)$ https://new-example.com/$1 [R=301,L]
# 例2: /2023/05/post-name/ → /post-name/ (年月入りパーマリンクから投稿名のみへ)
RewriteRule ^[0-9]{4}/[0-9]{2}/([^/]+)/?$ /$1/ [R=301,L]
# 例3: カテゴリーベースの変更 /category/news/... → /topics/news/...
RewriteRule ^category/(.+)$ /topics/$1 [R=301,L]
</IfModule>
書くときの注意点は次のとおりです。
.htaccessでは、マッチ対象のパスの先頭に/が付きません。同じルールをバーチャルホストに書く場合は^/[0-9]{4}/...のように/を含めます。- 例2のパターンは「4桁/2桁/1階層」にだけ一致させています。
^[0-9]{4}/(.*)$のように広く書くと、年別アーカイブ(/2023/)や月別アーカイブまで巻き込みます。パターンはできるだけ狭くします。 RewriteCondは直後の1つのRewriteRuleにだけかかります。複数のルールに同じ条件を使いたいときは、条件をそれぞれの前に書きます。- ルールは上から順に評価されます。ドメイン移転と構造変更を同時に行う場合、上の例では旧ドメインの年月入りURLが「新ドメインの年月入りURL → 新ドメインの投稿名URL」と2回転送されます。1回で済ませたいときは、例1より上に、旧ドメインを条件にして新ドメインの新パスへ直接転送するルールを足します。
規則にならないURLは対応表(RewriteMap)にまとめる
1件ずつ対応を決める URL が数十件を超えると、RewriteRule を並べる方法は読みにくく、処理も遅くなります。サーバー設定を編集できるなら、対応表をテキストファイルにして RewriteMap で参照する方法が向いています。
RewriteMap の定義は、サーバー設定またはバーチャルホストの中にしか書けません(.htaccess では定義できません)。定義した表を使うルールはバーチャルホストに書くのが分かりやすいでしょう。
# /etc/apache2/sites-available/example.com.conf の <VirtualHost> 内
RewriteEngine On
RewriteMap oldurls "txt:/etc/apache2/redirect-map.txt"
RewriteCond ${oldurls:%{REQUEST_URI}|NONE} !=NONE
RewriteRule ^ ${oldurls:%{REQUEST_URI}} [R=301,L]
対応表は「旧パス 新URL」を空白区切りで1行ずつ書きます。
# /etc/apache2/redirect-map.txt
/old-guide/ /guide/wordpress-setup/
/archives/123 /blog/backup-basics/
/service/plan-a.html https://new-example.com/plans/
RewriteCond の部分は「表に該当するキーがあれば」という条件です。該当しないときは既定値の NONE が返るので、ルールは適用されません。表に書くキーは %{REQUEST_URI}(先頭が / で、クエリ文字列を含まないパス)と同じ形にそろえます。末尾のスラッシュの有無が違うと一致しないため、旧URLに両方の形が使われていた場合は両方を書いておくと漏れを防げます。
テキスト形式の表は、ファイルの更新日時が変わると読み直される仕組みです。件数が非常に多い場合は、Apache に付属する httxt2dbm で DBM 形式に変換し、dbm: で参照すると検索が速くなります。変更後は apache2ctl configtest で構文を確かめてから再読み込みしてください。
サーバー設定を編集できないレンタルサーバーでは RewriteMap は使えません。その場合は .htaccess に RewriteRule を並べるか、WordPress 内で転送を管理できる「Redirection」などのプラグインを使う方法があります。プラグインは管理画面から登録や404の確認ができる反面、WordPress が起動してから転送するため、Apache で処理するより応答はわずかに遅くなります。
フラグの使い分け
一括転送でよく使うフラグを整理します。
R=301: 恒久的な転送として外部リダイレクトします。Rだけだと 302 になります。L: このルールに一致したら、以降のルールを処理しません。転送ルールには基本的に付けます。NC: 大文字・小文字を区別せずに一致させます。RewriteCond側では[NC]を条件に付けます。QSD: 旧URLのクエリ文字列を捨てます。何も指定しないと、置換先にクエリ文字列を書いていない限り旧URLのクエリ文字列はそのまま引き継がれます。QSA: 置換先に書いたクエリ文字列に、旧URLのクエリ文字列を追加します。NE: 置換先に含まれる#や%をエスケープせずに出力します。転送先にページ内リンク(#section)を含めたいときに使います。
クエリ文字列で記事を表示していた旧URL(/?p=123 など)を転送したい場合、RewriteRule のパターンにはクエリ文字列が含まれないため、RewriteCond %{QUERY_STRING} で判定します。転送先にクエリ文字列を残したくないなら QSD を付けます。
日本語を含む URL は、mod_rewrite が URL エンコードを戻した状態のパスで照合する点に注意が必要です。設定ファイルを UTF-8 で保存したうえで、実際に curl で転送結果を確かめてください。件数が多い場合は、対応表かプラグインで管理するほうが間違いを見つけやすくなります。
公開前に転送をテストする
ルールを書いたら、まず R=302(一時的な転送)で試します。301 はブラウザが強くキャッシュするため、間違った転送先を一度でも 301 で返すと、ルールを直しても手元のブラウザでは古い転送先に飛び続けることがあります。
確認はブラウザより curl が便利です。
# 1件の転送結果を見る(Location ヘッダーとステータスコード)
curl -sI https://example.com/2023/05/post-name/
# 転送を最後までたどり、途中の経由先も表示する
curl -sIL https://example.com/old-guide/ | grep -iE "^(HTTP|location)"
2つ目のコマンドで HTTP/... 301 が2回以上続く場合、転送が連鎖しています。たとえば「http の旧URL → https の旧URL → 新URL」のような連鎖です。ブラウザはたどれますが、無駄な往復が増えるため、最終的な転送先へ1回で飛ぶよう順序や置換先を見直します。
旧URLの一覧が手元にあるなら、シェルで繰り返して全件の結果をファイルに書き出し、ステータスと転送先が想定どおりかを表計算ソフトで突き合わせると漏れを見つけやすくなります。
# old-urls.txt に1行1URLで旧URLを並べておく
while read -r url; do
printf '%s\t%s\n' "$url" "$(curl -s -o /dev/null -w '%{http_code}\t%{redirect_url}' "$url")"
done < old-urls.txt > redirect-check.tsv
すべて想定どおりになったら R=301 に切り替えます。
公開後に見ておくこと
公開してから数週間は、アクセスログやエラーログで 404 になっている旧URLを確認し、漏れていたものを対応表に足していきます。Google Search Console のページのインデックス登録レポートでも、見つからない URL やリダイレクトの状況を確認できます。転送の効果がどの程度の期間で検索結果に反映されるかはサイトによって異なり、順位が保たれることも保証されません。移転後もしばらくは旧ドメインを手放さず、転送を維持してください。
ルールが効かない、ループするといった場合は、mod_rewrite のトレースログ(LogLevel で rewrite:trace を指定)で、どのルールがどう評価されたかを追えます。トレースログは大量に出力されるため、調べ終わったら元に戻します。
まとめ
- 旧URLの一覧を先に作り、規則で変換できるグループは正規表現、1件ずつのものは RewriteMap の対応表で処理する。
.htaccessでは WordPress のブロックより上に書き、パスの先頭に/が付かない点とパターンを狭く書くことに注意する。RewriteMapの定義はサーバー設定かバーチャルホストにしか書けない。編集できない環境ではルールを並べるかプラグインを使う。- 302 で試し、
curl -sILで連鎖やループがないことを確かめてから 301 にする。公開後は404のログを見て漏れを足す。 - ディレクティブやフラグは Apache 2.4 系の前提です(2026年10月現在)。