【WordPress】301リダイレクトを.htaccessで行う方法と注意点

WordPress ロゴ

少し前にブログのカテゴリを整理したとき、URLが変わってしまったページがいくつか出ました。そのままだと以前のURLでアクセスしてきた人がエラーに行き着いてしまうので、.htaccessで301リダイレクトを設定することに。

ところが、書き方は合っているはずなのに何度やっても転送されず、しばらくハマってしまいました。原因は意外なところにあったので、自分用の備忘録も兼ねて、WordPressで作ったサイトで301リダイレクトを.htaccessで設定する方法と、つまずきやすい注意点をまとめておきます。

301リダイレクトとは

301リダイレクトは、あるページを別のURLへ「恒久的に移転した」として転送する仕組みです。ブラウザにも検索エンジンにも「このURLはもう新しい方に移りましたよ」と伝わるので、旧URLに集まっていた評価(被リンクなど)も基本的には新URLへ引き継がれます。

似たものに302リダイレクトがありますが、こちらは「一時的な転送」を表すコードです。メンテナンス中に別ページを見せる、といった一時的な用途向けなので、URLが完全に変わったときに302を使うと評価が引き継がれにくくなります。

サイトのドメイン移転、http→httpsへの切り替え、サイト構成の変更によるURL変更など、URLが元に戻らない変更のときは301を使う、と覚えておけば間違いありません。

.htaccessで301リダイレクトを書く3つの方法

.htaccessでの301リダイレクトには、大きく分けて3つの書き方があります。用途に応じて使い分けます。なお、いずれもApache向けの設定です(Nginxの場合は.htaccessを使わないので、後述の別の対応が必要です)。

1. Redirect(1ページを丸ごと転送)

「このURLをこのURLへ」と1対1で単純に飛ばしたいときは、mod_aliasRedirectディレクティブが一番手軽です。

Redirect 301 /old-page.html https://www.example.com/new-page.html

「301」がステータスコードの指定、続く2つがリダイレクト元のパスと転送先のURLです。Redirect permanentと書いても意味は同じで、どちらも301を返します。

2. RedirectMatch(正規表現でまとめて転送)

ディレクトリ配下をまとめて飛ばしたい、といった場合は正規表現が使えるRedirectMatchが便利です。

RedirectMatch 301 ^/docs/(.*)$ https://www.example.com/documents/$1

この例では、/docs/以下のパスをそっくり/documents/以下へ引き継いで転送します。$1にマッチした部分が入ります。

3. RewriteRule(mod_rewriteで柔軟に)

条件を細かく指定したいときはmod_rewriteを使います。WordPressのパーマリンク機能もこれで動いているので、なじみがある方も多いはずです。当ブログでは、[firefox]というカテゴリを廃止して[webinfo]へ統合したとき、次のように書きました。

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^category/firefox https://www.example.com/category/webinfo [R=301,L]
</IfModule>

^category/firefoxがリダイレクト元、後ろのURLが転送先です。先頭の^は「文字列の始まり」を表す正規表現で、ドメイン以降のパスの先頭と考えればOKです。末尾の[R=301,L]のうちR=301が301リダイレクトの指定、Lは「ここでルールの処理を打ち切る」という意味です。

Apache公式のドキュメントでも、単純な1対1の転送ならmod_rewriteよりRedirectRedirectMatchの方が向いている、とされています。凝った条件分岐が要らないなら、まずはこの2つを検討するとシンプルに済みます。

【最重要】書く場所に注意 ── WordPressブロックの外に書く

ここが、私がハマった最大の落とし穴でした。WordPressでサイトを作ると、.htaccessには次のようなブロックが自動で書き込まれています。

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

この# BEGIN WordPressから# END WordPressまでのブロックは、WordPressが更新のたびに自動で書き換える領域です。ここにリダイレクトを書き足しても消えてしまう可能性がありますし、そもそもブロック内のルールで[L]によって処理が打ち切られ、自分の書いたリダイレクトまで到達しないことがあります。

私が動かなくてハマっていたのも、まさにこれが原因でした。リダイレクトの記述を# BEGIN WordPressブロックより上(.htaccessの最上部)に書いたところ、あっさり動くようになったのです。

Redirect 301 /old-page.html https://www.example.com/new-page.html

# BEGIN WordPress
<IfModule mod_rewrite.c>
(WordPressが自動生成する記述)
</IfModule>
# END WordPress

自分のリダイレクトはブロックの外、それも上に置く。これが基本です。結構な盲点だと思うので、うまく動かないときはまず書く場所を確認してみてください。

http→httpsへの常時SSL化リダイレクト

サイト全体をhttpsへ一本化したいときも301リダイレクトを使います。mod_rewriteでの定番はこの書き方です。

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^(.*)$ https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L]
</IfModule>

RewriteCond %{HTTPS} !=onで「httpsでアクセスされていないとき」だけ、httpsのURLへ転送しています。これもWordPressブロックの外に書くのが安全です。

リダイレクトループに注意

設定を間違えると、AへBへAへ…と延々と転送が繰り返される「リダイレクトループ」になり、ブラウザに「リダイレクトが繰り返し行われました」というエラーが出ます。

よくある原因は、転送元と転送先が結果的に同じ条件にマッチしてしまうケースです。上のhttps化の例のように、「まだ条件を満たしていないときだけ転送する」条件(RewriteCond)を必ず付けること、そして転送先が転送元のパターンに再び引っかからないよう気をつけることが、ループ回避のポイントです。

プラグインで設定する方法もある

.htaccessを直接触るのが不安、あるいはサーバーの管理画面から.htaccessを編集しづらい場合は、リダイレクト管理用のプラグインを使う手もあります。定番はRedirectionで、管理画面から元URLと転送先を入力するだけで301リダイレクトを設定でき、どのリダイレクトが何回使われたかのログも見られます。

ただ、プラグインを増やすとその分サイトが重くなったり、管理項目が増えたりします。設定するリダイレクトが数件で、.htaccessを編集できる環境なら、私は素の.htaccessで済ませる派です。件数が多い、非エンジニアも運用する、といった場合はプラグインの方が管理しやすいので、状況で選ぶといいと思います。

Nginxの場合は.htaccessが使えない

ここまではApache前提の話でした。サーバーがNginxの場合は.htaccess自体が存在せず、上の記述は効きません。Nginxではサーバー設定ファイル(nginx.conf など)にreturn 301rewriteディレクティブで記述します。この編集にはサーバー側の権限が必要になることが多いので、レンタルサーバーなら管理画面のリダイレクト機能や、先ほどのプラグインを使うのが現実的です。自分のサーバーがApacheかNginxか分からないときは、まず契約先の仕様を確認してみてください。

まとめ

最後に、つまずかないためのポイントを整理しておきます。

  • 単純な1対1の転送はRedirect 301、パターンでまとめるならRedirectMatch 301、細かい条件が要るならRewriteRule [R=301,L]
  • 自分のリダイレクトは必ず# BEGIN WordPressブロックの外、しかも上に書く
  • https化などサイト全体の転送ではRewriteCondで条件を付け、リダイレクトループを避ける
  • Apache前提。NginxやプラグインでもOKなので、環境に合わせて選ぶ

動かないときの原因は、記述そのものより「書く場所」であることが本当に多いです。まずはそこを疑ってみてください。