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 がもう一度同じルールに一致しています。よくある原因は次の順です。

  1. SSL 終端の前段があるのに %{HTTPS} で判定している(上記の X-Forwarded-Proto を使う)
  2. WordPress のサイトアドレスと .htaccess の統一先が食い違っている
  3. 転送先が転送元のパターンにも一致している(^blog/(.*)$ を /blog/new/$1 に転送するなど)
  4. キャッシュ系・リダイレクト系のプラグインが、別の転送を出している

逆にルールが効かない場合は、書いた位置(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に切り替える

お気軽にご相談ください

お見積りへ お問い合わせへ
▲