htaccessでリダイレクトを設定する書き方やコードの意味

サイトを運営していると、wwwの有無やindex.html・index.phpの有無、URL末尾のスラッシュの有無など、本来は同じページのはずなのに複数のURLでアクセスできてしまうことがあります。

放置すると同じ内容のページが別々のURLとして評価されてしまうため、.htaccessでリダイレクトを設定してURLを1つに統一するのが基本の対処法です。

URLの正規化とリダイレクトを実現する2つの書き方

URLが重複しやすい典型的なパターンは、主に次の3つ。

  • wwwのあり・なし
  • ファイル名(index.html・index.php)のあり・なし
  • URL末尾のスラッシュのあり・なし

これらを1つのURLに統一するのも、他サイトへの移転やページ単位の転送も、Apacheでは大きく分けて次の2つのモジュールで実現します。

  • mod_aliasが提供するRedirect・RedirectMatch(1つのURL、またはそれに近いパターンを別の場所へ転送するためのシンプルな命令)
  • mod_rewriteが提供するRewriteRule(正規表現や条件分岐を組み合わせて、より複雑なURL変換を行うための命令)

Apache公式ドキュメントでも、単純な転送であれば複雑なRewriteRuleではなくRedirectやRedirectMatchを使うことが推奨されています。まずはこの2つの違いを押さえたうえで、それぞれの書き方を見ていきます。

RewriteRuleにはもう1つ、Redirectにはない役割があります。ブラウザのアドレスバーのURLはそのままに、サーバー内部だけで表示するファイルを別のものに差し替える「内部的な書き換え」です。転送先を伴わない(Rフラグを付けない)RewriteRuleはこちらの動きになり、WordPressのパーマリンク機構もこの仕組みを使って、実際には存在しないURLへのアクセスをindex.phpに渡しています(詳しくは後述の「WordPressで.htaccessにリダイレクトを追記するときの注意点」にて)。

mod_aliasで書くシンプルな転送

広告

1ページだけ、または特定のディレクトリ配下をまるごと別の場所に転送したいだけなら、Redirectで十分です。

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

ステータスコード(301の部分)を省略すると302(一時的な転送)として扱われるため、恒久的に移転する場合は省略せず301またはpermanentを明記します。

Redirectはパスを保持したまま転送する性質があり、/old-dir/を転送先に指定すると/old-dir/page.htmlのような配下のURLも自動的に転送されます。ディレクトリ単位の移転で、配下のファイル名を変えずにそのまま新しい場所へ持っていきたい場合に向いています。

正規表現を使ってもう少し柔軟にマッチさせたい場合はRedirectMatchを使います。

広告
RedirectMatch 301 ^/old-dir/(.*)$ https://example.com/new-dir/$1

mod_rewriteで書く柔軟な転送

条件を細かく指定したい場合や、ドメインやプロトコル(http・https)によって挙動を変えたい場合はmod_rewriteのRewriteRuleを使います。

基本の書き方とRewriteEngineの有効化

RewriteRuleを使うには、まずRewriteEngine Onでmod_rewriteの機能を有効にする必要があります。サーバー側でmod_rewrite自体が読み込まれていない環境でエラーにならないよう、<IfModule mod_rewrite.c>で全体を囲むのが一般的です。

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^old-page\.html$ https://example.com/new-page.html [R=301,L]
</IfModule>

正規表現と後方参照(バックリファレンス)

RewriteRuleのパターン部分は正規表現で書きます。.(ドット)はそのままだと「任意の1文字」を意味してしまうので、ファイル名の区切りとしての「.」に一致させたい場合は\.のようにエスケープします。

パターン内の(...)で囲んだ部分は、転送先の中で$1(1つ目の括弧)、$2(2つ目の括弧)として参照できます。これを使うと、ディレクトリ名だけを変えて配下のファイル名はそのまま引き継ぐ、といった転送が書けます。

RewriteRule ^old-dir/(.*)$ https://example.com/new-dir/$1 [R=301,L]

なお、RewriteCond(次の条件を満たすときだけ直後のRewriteRuleを実行する、という条件指定)の中で括弧を使った場合は、$1ではなく%1で参照します。$はRewriteRule自身のパターンから、%はその手前のRewriteCondから、という違いです。RewriteCondを複数行続けた場合、初期状態ではすべての条件を満たしたときだけ次のRewriteRuleが実行されます(AND条件)。いずれかを満たせばよい場合は、条件の末尾に[OR]を付けます。

よく使うフラグ

RewriteRuleの末尾の[ ]内に指定するフラグのうち、リダイレクトでよく使うものは次のとおりです。

  • RまたはR=301・R=302(実際にブラウザへリダイレクト応答を返す。数字を省略すると302扱いになるため、恒久的な転送ならR=301と明記する)
  • L(このルールに一致したら、以降のルールを処理せず終了する)
  • NC(パターンの大文字・小文字を区別しない)
  • NE(転送先URLを再エスケープしない)
  • OR(RewriteCondに付け、直前の条件との「または」判定にする)

URLの正規化パターンごとの書き方

wwwあり・なしを統一

www なしで統一させたい場合

RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]

www ありで統一させたい場合

RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*)$ https://www.%{HTTP_HOST}/$1 [R=301,L]

すでに記事が公開されていて、その中に絶対パスでのリンクを設定しているなら、見つけ次第修正しておきましょう。リダイレクトが設定されていれば自動的に転送されますが、不要なリダイレクトはない方が良いです。

index.htmlやindex.phpを省略したURLに統一する

静的なHTMLファイルで運用しているサイトで、/page/index.htmlのようなURLを/page/に統一したい場合は次のように書きます。

RewriteRule ^(.*)index\.(html|php)$ /$1 [R=301,L]

ルート直下のindex.htmlにアクセスした場合は$1が空になるため/に、page/index.htmlの場合は$1がpage/になるため/page/にリダイレクトされます。なお、WordPressのパーマリンクはこの仕組みとは別物なので、この書き方が必要になるのは主に静的サイトの場合です。

末尾のスラッシュを統一する

末尾のスラッシュを取り除いて統一する例です。

RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)/$ /$1 [R=301,L]

%{REQUEST_FILENAME} !-d(リクエストされたパスが実在するディレクトリではない、という条件)を付けているのは、実在するディレクトリまでスラッシュなしに転送してしまうのを防ぐためです。

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

「http」から「https」へ

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

サーバーの手前にCDNやロードバランサー(Cloudflareなど、SSL通信をサーバーより手前で終端する仕組み)がある場合、サーバー自身から見るとhttpsアクセスでも%{HTTPS}が常にoffになっていることがあり、この書き方だとリダイレクトが無限ループしてしまうことがあります。その場合は、CDN側が付与するX-Forwarded-Protoヘッダーを見て判定します。

RewriteCond %{HTTP:X-Forwarded-Proto} =http [NC]
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

一部のページだけをhttps化する

サイト全体ではなく、特定のディレクトリ配下だけを先にhttps化したい場合は、RewriteCondを[OR]でつなげて対象のパスを列挙します。

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteCond %{REQUEST_URI} ^/form/ [OR]
RewriteCond %{REQUEST_URI} ^/contact/
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

301リダイレクトと302リダイレクトの違い

301(恒久的な転送)は、検索エンジンに対して「このページの評価は新しいURLに引き継いでよい」と伝える意味を持ちます。ページを完全に移転する場合は基本的に301を使います。

302(一時的な転送)は、検索エンジンに元のURLをそのまま残しておいてほしい場合に使います。キャンペーンページやメンテナンス中の案内先など、いずれ元のURLに戻す予定がある場合に向いています。

ブラウザは301を302よりも積極的にキャッシュする傾向があります。設定を変更したのに反映されているか確認しづらいときは、シークレットウィンドウで開くか、後述するcurlコマンドで直接ステータスコードを確認すると確実です。

サイトを丸ごと移転する場合の書き方(ドメイン変更)

元のサイトと新しいサイトでファイル構造が完全に同じであれば、Redirect をルートディレクトリの .htaccess に1行書くだけで済みます。

Redirect permanent / https://new-domain.example.com/

ファイル構造が変わっていて、ページごとに転送先を個別に指定したい場合は RewriteRule を使います。

RewriteEngine On
RewriteRule ^old-page\.html$ https://new-domain.example.com/new-page.html [R=301,L]

WordPressで.htaccessにリダイレクトを追記するときの注意点

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

この最後の RewriteRule . /index.php [L] は「実在しないファイル・ディレクトリへのアクセスは全部index.phpに渡し、そこで処理を終える」というルールで、Lフラグによってこれより後ろのルールは処理されません。そのため、独自の転送ルールをこのブロックより後ろに書いても実行されず、# BEGIN WordPressより前に書く必要があります。

WordPressはパーマリンク設定を保存し直すたびにこのブロックを書き換えますが、# BEGIN WordPressから# END WordPressまでの内側だけを書き換え、その前後に書いた内容はそのまま保持されます。ただし、前後に書いたコメントの中に# BEGIN WordPressという文字列がそのまま含まれていると誤認識される可能性があるため、コメントの書き方には注意が必要です。

なお、サーバーの環境によってはPHPにAuthorizationヘッダーが渡らず、WordPress環境でBASIC認証と組み合わせるとうまく動かないことがあります。その場合はRewriteBaseの前に次の1行を追加してヘッダーを引き継がせます。

RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

BASIC認証自体の設定方法は、htaccessを使ったBASIC認証で詳しく解説しています。

.htaccessを直接編集せず管理画面から転送ルールを設定したい場合は、Redirectionプラグインを使う方法もあります。404になったアクセスのログを取りながら転送ルールを追加できるため、直接編集に不安がある場合の選択肢になります。

.htaccessの設定が反映されない・500エラーになるときの確認ポイント

RewriteEngine Onを書いたはずなのに転送されない、あるいは真っ白な500エラーになる場合は、次の順に確認してみてください。

  1. <IfModule>の閉じ忘れ、フラグを囲む[ ]の書き間違いなど、単純な構文・記述ミスがないか確認します。ファイルの最後に空の1行が入っていないと、サーバーによってはエラーになることもあるので、末尾に改行を1つ入れておくと安全です。
  2. 全角スペースや全角文字が紛れ込んでいないか、文字コードがUTF-8(BOM無し)になっているか、改行コードがLFになっているかを確認します。
  3. 国内の主要なレンタルサーバーでは通常デフォルトで有効になっていますが、自前で管理しているVPSやサーバーでは、Apache本体の設定(AllowOverride)で.htaccessによるリダイレクトの上書きが許可されているか、そもそも利用しているサーバーで.htaccess自体が使える契約かどうかを確認します。
  4. ブラウザに「このページは正しく機能していません」「リダイレクトが多すぎます」といったリダイレクトループの表示が出る場合、httpsやwwwの統一ルール同士が矛盾していないか、前述のCDN配下での%{HTTPS}判定の問題が起きていないかを確認します。
  5. 設定を直したはずなのに反映されない場合、ブラウザが古いリダイレクト結果をキャッシュしている可能性があります。curlコマンドでステータスコードと転送先を直接確認すると、キャッシュの影響を受けずに切り分けられます。
curl -I https://example.com/old-page.html
広告

カテゴリー : WEB全般