2026年6月1日
2026年6月1日
Apacheのmod_rewriteでURLリダイレクトする方法|301転送の書き方と確認手順
Apache で URL を転送するには、mod_rewrite の RewriteCond と RewriteRule を使います。この記事では、http から https への統一、www の有無の統一、ページやディレクトリの移転、クエリ文字列つき URL の転送といった定番パターンを、そのまま使える形で紹介します。あわせて、.htaccess 特有の書き方の違い、ループの防ぎ方、302で試してから301に切り替える確認手順も説明します。
mod_rewrite が使える状態かを確かめる
最初に、mod_rewrite が読み込まれているかを確認します。VPS なら次のコマンドで分かります。
# Debian/Ubuntu系
sudo apache2ctl -M | grep rewrite
# RHEL/AlmaLinux系
sudo httpd -M | grep rewrite
rewrite_module (shared) と表示されれば有効です。Debian/Ubuntu 系で表示されない場合は sudo a2enmod rewrite を実行し、sudo systemctl restart apache2 で再起動します。RHEL 系は標準で読み込まれている構成が多いですが、/etc/httpd/conf.modules.d/ の設定で無効にされていないかを確認してください。
.htaccess に書く場合は、本体設定の AllowOverride で FileInfo(または All)が許可されている必要があります。WordPress のパーマリンクが「投稿名」などで正しく動いているなら、この条件は満たされています。
単純な転送なら Redirect でも足りる
mod_rewrite は高機能ですが、1対1の単純な転送なら mod_alias の Redirect で書いたほうが読みやすくなります。Apache の公式ドキュメントにも、mod_rewrite を使わずに済む場面の解説があります。
Redirect 301 /old-page/ https://example.com/new-page/
一方、「ホスト名が www のときだけ」「https でないときだけ」「特定のクエリ文字列があるときだけ」のように条件をつけたい場合は mod_rewrite が必要です。同じ .htaccess の中で Redirect と RewriteRule を混在させると処理順が読みにくくなるため、どちらか一方にそろえるのがおすすめです。WordPress の .htaccess にはすでに mod_rewrite のルールがあるので、この記事では mod_rewrite にそろえて説明します。
書き方の基本:RewriteCond と RewriteRule
RewriteEngine On
RewriteCond 検査する値 条件パターン [フラグ]
RewriteRule 対象パターン 置換先 [フラグ]
RewriteRule は「URL のパスがパターンに一致したら置換先へ書き換える(転送する)」命令です。その直前に並べた RewriteCond は、すべて満たしたときだけ次の RewriteRule を有効にする条件になります。条件は直後の RewriteRule 1つにしかかからない点が、最初につまずきやすいところです。
よく使うフラグは次のとおりです。
R=301:ブラウザに301(恒久的な移転)を返して転送します。Rだけだと302になります。L:このルールで処理を打ち切ります。転送ルールには基本的に付けます。NC:大文字・小文字を区別せずに比較します。QSD:元のクエリ文字列を捨てます(Apache 2.4 以降)。QSA:置換先にクエリ文字列がある場合でも、元のクエリ文字列を後ろに足します。
.htaccess ではパスの先頭に「/」が付かない
同じルールでも、.htaccess に書くか、VirtualHost などの本体設定に書くかで、RewriteRule が比較するパスの形が変わります。.htaccess では、そのファイルがあるディレクトリからの相対パスになり、先頭の / が付きません。
# .htaccess(ドキュメントルート直下)に書く場合
RewriteRule ^old-page/?$ /new-page/ [R=301,L]
# VirtualHost に書く場合
RewriteRule ^/old-page/?$ /new-page/ [R=301,L]
ネットで見つけたルールが動かないときは、どちらの場所向けに書かれたものかを確認してください。
クエリ文字列は RewriteRule では見えない
RewriteRule のパターンは ? より前のパス部分だけを比較します。?id=10 のようなクエリ文字列で分岐したいときは、RewriteCond %{QUERY_STRING} を使います。
定番の転送パターン
以下はドキュメントルート直下の .htaccess に書く前提の例です。example.com は自分のドメインに置き換えてください。WordPress サイトでは、# BEGIN WordPress のブロックより上に書きます。WordPress のブロックは存在しない URL をすべて index.php に回すため、その下に書いた転送ルールは期待どおりに動かないことがあります。
http を https に統一する
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
^ はすべてのパスに一致します。%{REQUEST_URI} には先頭の / を含むパスが入るので、パスを保ったまま https へ転送できます。
CDN やロードバランサーで SSL を終端している構成では、Apache から見ると通信がすべて http のため、%{HTTPS} が常に off になり、無限ループになります。その場合は前段が付ける X-Forwarded-Proto ヘッダーで判定します。
RewriteCond %{HTTP:X-Forwarded-Proto} =http
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
ヘッダー名や付与の有無はサービスによって違うため、利用している CDN・ロードバランサーのドキュメントで確認してください。
www あり・なしを統一する
RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]
転送先にスキームまで書いておくと、http→https と www 統一が1回の転送で済み、転送の連鎖(チェーン)を減らせます。WordPress の「設定 → 一般」にあるサイトアドレスも、統一後の形と一致させておきます。食い違うと、WordPress 側の転送と .htaccess の転送が打ち消し合ってループすることがあります。
個別ページ・ディレクトリを移転する
# 1ページだけ移転
RewriteRule ^old-page/?$ /new-page/ [R=301,L]
# /blog/ 以下を /news/ 以下へ、パスを保って移転
RewriteRule ^blog/(.*)$ /news/$1 [R=301,L]
(.*) で取り出した部分が $1 に入ります。/?$ は末尾スラッシュの有無の両方に一致させるための書き方です。
クエリ文字列つきの旧URLを転送する
旧システムの /item.php?id=10 を /products/10/ へ移す例です。
RewriteCond %{QUERY_STRING} ^id=([0-9]+)$
RewriteRule ^item\.php$ /products/%1/ [R=301,L,QSD]
RewriteCond で取り出した値は $1 ではなく %1 で参照します。QSD を付けないと、転送先にも ?id=10 が残ります。
302で試してから301に切り替える
301は「恒久的な移転」なので、ブラウザがその転送を長く記憶します。間違ったルールを301で出すと、修正してもブラウザ側に古い転送が残り、確認が難しくなります。最初は R=302 で動作を確かめ、問題が無いことを確認してから R=301 に変えると安全です。
確認には curl を使います。ブラウザのキャッシュに左右されず、何回転送されたかも分かります。
# 1回分の応答だけを見る(Location ヘッダーが転送先)
curl -I http://www.example.com/old-page/
# 転送を最後までたどり、途中の応答もすべて表示する
curl -IL http://www.example.com/old-page/
curl -IL の結果で、最終的に 200 になっているか、途中で何回転送されているかを見ます。転送が3回、4回と重なっている場合は、ルールを1回で目的の URL に届く形に整理します。
ループや「効かない」ときの調べ方
ブラウザに「リダイレクトが繰り返し行われました」と表示される場合は、転送先の URL がもう一度同じルールに一致しています。よくある原因は次の順です。
- SSL 終端の前段があるのに
%{HTTPS}で判定している(上記の X-Forwarded-Proto を使う) - WordPress のサイトアドレスと .htaccess の統一先が食い違っている
- 転送先が転送元のパターンにも一致している(
^blog/(.*)$を/blog/new/$1に転送するなど) - キャッシュ系・リダイレクト系のプラグインが、別の転送を出している
逆にルールが効かない場合は、書いた位置(WordPress ブロックより上か)、パスの先頭の /、RewriteCond がかかる範囲を見直します。
それでも分からないときは、VPS であれば mod_rewrite の処理経過をログに出せます。Apache 2.4 では古い RewriteLog は廃止されており、LogLevel で指定します。この設定は .htaccess ではなく本体設定に書きます。
# VirtualHost 内に一時的に追加(確認後は削除)
LogLevel alert rewrite:trace3
エラーログに、どのルールがどの値と比較されたかが記録されます。ログが急速に増えるので、本番環境では短時間だけ有効にし、確認が終わったら戻してください。
SEO の観点で気を付けること
Google 検索セントラルでは、恒久的な移転には301などのサーバー側リダイレクトを使い、移転後もリダイレクトを長期間(目安として少なくとも1年)残すことが推奨されています(2026年10月現在)。転送を設定したら、内部リンクやサイトマップも新しい URL に書き換えておくと、転送を経由するアクセスが減ります。リダイレクトは順位を上げるための手段ではなく、移転前の評価とユーザーを新しい URL へ引き継ぐための手段です。
WordPress のサイト内でページの転送を多数管理したい場合は、Redirection のような転送管理プラグインを使う方法もあります。.htaccess を直接編集せずに済み、転送の一覧や404の記録を管理画面で確認できます。サーバー全体にかかわる https 化や www 統一は .htaccess、記事単位の移転はプラグイン、と役割を分けると管理しやすくなります。
まとめ
- 単純な1対1の転送は
Redirectでも書けるが、条件付きの転送は mod_rewrite を使う - RewriteCond は直後の RewriteRule 1つにだけかかる。クエリ文字列は
%{QUERY_STRING}で判定し、%1で参照する - .htaccess ではパスの先頭に
/が付かない。WordPress サイトでは# BEGIN WordPressより上に書く - SSL 終端の前段がある構成は
X-Forwarded-Protoで判定してループを防ぐ - 302で試し、
curl -ILで転送回数と最終ステータスを確認してから301に切り替える