1分で読了チュートリアル

htaccessでの301リダイレクト:ルール、実行順序、クエリ文字列

.htaccessでの301リダイレクトは、mod_aliasのたった1行です。その1行と、代わりにRewriteRuleが適切な場面、そして実行順序が行の位置より重要である理由を解説します。

Marius Voß
DevRel · edge infra
htaccessの301リダイレクトを、mod_aliasの1行とmod_rewriteのルールを並べて示し、両者の実行順序を表した図

.htaccessでの301リダイレクトは、たった1行です。

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

これをドキュメントルートの.htaccessファイルに保存すれば、即座に有効になります。Apacheはリクエストごとにこのファイルを読み込むため、再起動もデプロイも不要です。このディレクティブはmod_aliasのもので、実質的にすべてのApacheインストールに存在し、Locationヘッダー付きの301 Moved Permanentlyを送ります。仕事はそれだけです。

Apacheのリダイレクトで問題になることのほとんどは、ここから先で起きます。Redirectで済むところでRewriteRuleに手を伸ばしてしまう、2つのモジュールを1つのファイルに混在させて誰も予想しない実行順序になる、あるいは途中でクエリ文字列を失ってしまう、といったことです。この記事では、覚えておく価値のある4つのルール、正しく見えるファイルを誤動作させる実行順序のトラップ、そしてブラウザに嘘をつかれずにリダイレクトをテストする方法を扱います。リダイレクトが存在し得る場所についての全体像は、URLをリダイレクトする方法を参照してください。

ほとんどのリダイレクトをカバーする1行

Redirectはステータス、マッチさせるパス、ターゲットを受け取ります。ターゲットは完全なURLでも、同じホスト上のパスでも構いません。

Redirect 301 /old-page /new-page
Redirect 301 /shop https://shop.example.com/
RedirectMatch 301 ^/blog/([0-9]{4})/(.*)$ /articles/$2

使う前に知っておく価値のある動作が2つあります。1つ目は、Apache自身のガイダンスがこれを正しい手段だと明言している点です。「1つのURL、あるいは一群のURLを別の場所へ移すこの種の単純なリダイレクトは、RewriteRuleではなくこれらのディレクティブを使って実現すべきである」。

2つ目は、人を驚かせるものです。「Redirectはパス情報を保持することを覚えておいてほしい。つまり、URL/oneに対するリダイレクトは、/one/two.html/one/three/four.htmlのような、その下にあるすべてのURLもリダイレクトする」。厳密に一致するパスだけを対象にしたいなら、RedirectMatch 301 ^/one$でアンカーします。そうしなければ、サブツリー全体をリダイレクトしたことになり、それはまさに意図した通りのこともあれば、非常に混乱する朝を生むこともあります。

RedirectMatchは正規表現版であり、人々がmod_rewriteを開く理由の大半をカバーします。キャプチャされたグループは$1$2などに入るため、ディレクトリのリネームや日付ベースのURL体系の変更も1行で済みます。

RewriteRuleが本当に必要になるとき

判断がパス以外の何かに依存するときは、mod_rewriteに手を伸ばします。ホスト、クエリ文字列、リクエストメソッド、クッキー、ユーザーエージェントはいずれもRewriteCondから見えますが、Redirectからは見えません。

RewriteEngine On
RewriteCond %{QUERY_STRING} (^|&)ref=oldpartner(&|$)
RewriteRule ^landing$ /partners/oldpartner? [R=301,L]

ディレクトリ単位のコンテキストには、コピー&ペーストされたルールがまったく何もしないという事態の大部分を占める落とし穴が1つあります。Apacheはマッチングの前にディレクトリのプレフィックスを取り除くため、パターンは先頭のスラッシュを一切見ません。「取り除かれるプレフィックスは常にスラッシュで終わる。つまりマッチングは先頭にスラッシュを持たない文字列に対して行われる。したがって^/を含むパターンは、ディレクトリ単位のコンテキストでは決してマッチしない」。

だからこそRewriteRule ^/old$ /new [R=301]は、誰かがそれを仮想ホストに貼り付ければ動作し、.htaccessでは黙って失敗するのです。スラッシュを取り除いて^old$とします。置換先が相対パスで、書き換えがサブディレクトリの中にある場合は、パスが何に対して相対的なのかをApacheに伝えるためにRewriteBaseも必要になることがあります。

実行順序のトラップ

これは、人々に午後をひとつ丸ごと持っていく類のものです。ファイル内の行の位置は、どちらのモジュールが先に実行されるかを決めません。

Apacheはこれを明確に文書化しています。「RedirectRewriteRuleを同じコンテキストで混在させる場合、その実行順序は出現する場所によって決まることに注意すること。サーバー/仮想ホストのコンテキストではmod_rewriteが先に実行され、ディレクトリ単位のコンテキスト(.htaccess)ではmod_aliasが先に実行される」。

これは2回読んでください。結果が直感に反するからです。**.htaccessファイルの中では、ファイルの末尾にあるRedirectが、先頭にあるRewriteRuleに勝ちます。**同一のルールを仮想ホストに移すと、勝者が逆転します。[L]フラグも救いにはなりません。それが意味するのは、mod_rewriteのこのパスにおける最後のルールであって、ファイル内の最後のルールではなく、他のモジュールに対する権限は何もないのです。

私が実践している現実的なルールは、1ファイルにつき1モジュールです。プロジェクトのどこかで条件が必要になるなら、そのリダイレクトはすべてmod_rewriteで行い、Redirectの行は削除します。「ステージングでは動くが本番では動かない」という不具合報告が生まれるのは、まさにこの混在したファイルからです。2つの環境がルールを異なるコンテキストに置いてしまうからです。

mod_aliasとmod_rewriteの実行順序を示す図:htaccessファイルの中では行の位置に関わらずmod_aliasが先に実行されることを表す

クエリ文字列:維持、置換、消去

マーケティングのリンクはクエリ文字列によって生き死にするので、この表は貼っておく価値があります。すべて言い伝えではなく文書化された動作です。

記述する内容元のクエリ文字列補足
Redirect 301 /a /b引き継がれるmod_aliasが代わりに追加してくれる
RedirectMatch 301 ^/a$ /b引き継がれる同じモジュール、同じ動作
RewriteRule ^a$ /b [R=301]そのまま通過する文書化されているデフォルト動作
RewriteRule ^a$ /b?src=x [R=301]自分のもので置き換わる自分のパラメータが優先される
RewriteRule ^a$ /b?src=x [R=301,QSA]自分のものと結合されるQSAが元のクエリ文字列を追加する
RewriteRule ^a$ /b? [R=301]消去される単独の?が消去する

デフォルト動作についてのApacheの表現はこうです。「デフォルトでは、クエリ文字列は変更されずに通過する」。フラグについては、[QSA]は「元のリクエストURLのクエリ文字列を、書き換えターゲットで作られたクエリ文字列に追加する」のに対し、[QSD]は受け取ったクエリ文字列を破棄します。絶対URIへリダイレクトする場合も、[QSD]を指定しない限りクエリ文字列は付いてきます。

これが防いでいる失敗は、静かで高くつくものです。クエリ文字列を置き換えるルールは、通過の途中でutm_sourceutm_campaignを取り除き、あなたの分析ツールはそのセッションを直接トラフィックとして帰属させ、何のエラーも出ません。誰かが春のキャンペーンが評価されなかった理由を尋ねるまで、誰も気づきません。GA4にUTMパラメータが表示されないでは、分析ツール側からの診断方法を解説しています。

サーバーの設定ファイルで何十件ものキャンペーン用リダイレクトを管理していることに気づいたら、それは雑用ではなくシグナルです。サーバー側のルールにはデプロイ、Apache設定のレビュー、シェルアクセスを持つ誰かが必要になります。キャンペーンリンクは自分で編集できる短縮リンクに移し.htaccessは本来得意な構造的なリダイレクトのために残しておきましょう。

HTTPSとwwwを2回ではなく1回のホップで

インターネットで最もコピーされているスニペットは、これを2つのルールブロックで行っており、つまりhttp://example.com/pageに到着した訪問者は2回リダイレクトされます。1回はTLSを追加するため、もう1回はwwwを追加するためです。2回のホップ、2回の往復、そしてそれぞれで少し弱まる信号です。

1つのルール、2つの条件、1回のホップです。

RewriteEngine On
RewriteCond %{HTTPS} !=on [OR]
RewriteCond %{HTTP_HOST} !^www\.example\.com$ [NC]
RewriteRule ^ https://www.example.com%{REQUEST_URI} [R=301,L]

CDNやロードバランサーの背後では、訪問者がHTTPSでアクセスしていても、TLSが上流で終端しているため、オリジンでは%{HTTPS}が通常offになります。代わりに%{HTTP:X-Forwarded-Proto}を検査します。

RewriteCond %{HTTP:X-Forwarded-Proto} !https

これを間違えると無限リダイレクトループを作ってしまいます。プロキシはHTTPSで送るのにオリジンはHTTPだと思い込み、HTTPSへリダイレクトし、それがブラウザが諦めるまで回り続けます。リダイレクトループの直し方では、レスポンスヘッダーからそれを診断する方法を解説しています。

2つの別々のhtaccessルールが2ホップのリダイレクトチェーンを生む場合と、1つのルールが1ホップで正規のHTTPS wwwのURLに到達する場合を比較した図

なぜ機能しないのか

私が確認する順番はこうです。

  1. AllowOverrideNoneになっている。 Apacheのドキュメントはデフォルト動作についてこう述べています。「これは、ディレクトリに対して明示的に有効化しない限り、.htaccessファイルが完全に無視されることを意味する」。ファイルの最初の行に意図的にゴミを入れてテストしてみましょう。500エラーが出ないなら、ファイルはまったく読み込まれておらず、中のすべてのルールは装飾にすぎません。
  2. ファイルの配置場所や名前が間違っている。ファイルは先頭のドットを含む.htaccessという名前で、リクエストが対応するディレクトリに置かれていなければなりません。親切心でhtaccess.txtとして保存してしまうエディタは、繰り返し発生する原因です。
  3. mod_rewriteがロードされていない。Redirectは動くのにRewriteRuleは黙って動かず、人々は1時間も自分の正規表現を見つめることになります。
  4. ブラウザが古い301をキャッシュしている。ChromeもFirefoxも、ブラウザプロファイルごとに永続的なリダイレクトを積極的にキャッシュするため、あなたがちょうどデプロイした修正はあなた自身には見えず、他の全員には正常に動いています。開発中はR=302を使い、ルールが正しくなったらR=301に切り替えましょう。
  5. ルールが自分自身のターゲットにマッチしている。ガードのないRewriteRule ^(.*)$ /index.php/$1が典型例です。宛先を除外する条件を追加しましょう。

ブラウザではなくcurlでテストする

1つのコマンドで、ステータス、ターゲット、何回のホップがかかったかが分かります。

curl -sIL https://example.com/old-page | grep -iE '^HTTP|^location'

出力は一連の流れとして読みます。200の前にHTTP/2 301が2行あれば2回のホップを意味し、それぞれのホップはモバイルネットワーク上の実際の訪問者にとって本物の往復です。リダイレクトに関するGoogleのドキュメントは、永続的なリダイレクトをURLを統合するための最も強力な信号として扱っており、1段階で到達することは3段階で到達することより明確に優れています。当社のリンクチェッカーはブラウザ上で同じチェーンを表示しますし、301対302リダイレクトでは迷ったときにどちらのステータスを送るべきかを解説しています。

今書いたパスだけでなく、重要なパスをテストしましょう。ルート、深い階層のパス、クエリ文字列付きのパス、そしてターゲット自体です。最後の1つは、訪問者より先にループを捕まえてくれます。

そもそも.htaccessを使うべきでないとき

Apache自身の立場は明確です。「メインのサーバー設定ファイルにアクセスできるなら、.htaccessファイルの代わりにそちらにすべての設定を置くべきである」。なぜなら、リクエストごとの解析には実際のコストがかかるからです。「.htaccessファイルを許可すると、実際に使っているかどうかにかかわらずパフォーマンスへの影響が生じる」。共有ホスティングでは選択の余地がありません。自分で管理するサーバーであれば、構造的なものについてはメインの設定の方が適した場所です

ルールをファイルからまるごと外しておくべき、もう1つの理由があり、それはパフォーマンスとは無関係です。印刷物、QRコード、あるいは誰かのスライドデッキに登場するリダイレクトは、あなたのWebサーバー、CMSの移行、そしておそらくホスティングプロバイダーよりも長く生き続ける必要があります。.htaccessの中のルールは、うっかりしたデプロイ1つで消えてしまう距離にあり、死んだ印刷リンクについて誰も報告してくれないまま、キャンペーンは終わってしまいます。構造的なリダイレクトはサーバーに置くべきで、キャンペーンや印刷物用のリンクは、数秒で編集でき、アクセスログをgrepしなくても計測できる場所に置くべきです。

コーナーストーンシリーズを読む

この記事はチュートリアルクラスターに属しています。全体像については、リダイレクトの種類がすべてのステータスコードとクライアント側の方式を解説し、URLをリダイレクトする方法がリダイレクトが存在し得る6つの場所を解説しています。

ブログの関連記事

よくある質問

.htaccessで301リダイレクトを作成するにはどうすればよいですか。

ドキュメントルートの.htaccessファイルに1行加えます:Redirect 301 /old-page https://example.com/new-page。Apacheはリクエストごとに.htaccessを読み込むため、ファイルを保存した瞬間にリダイレクトが有効になります。再起動もデプロイも不要です。このディレクティブはmod_aliasのもので、実質的にすべてのApacheインストールで有効になっています。

RedirectとRewriteRuleの違いは何ですか。

RedirectとRedirectMatchはmod_aliasのディレクティブで、ステータスコードとLocationヘッダーを送るという1つのことだけを行います。RewriteRuleはmod_rewriteのディレクティブで、判断を下す前にホスト、クエリ文字列、クッキー、ユーザーエージェントを検査できます。Apache自身のドキュメントも、単純なリダイレクトにはRewriteRuleではなくmod_aliasを使うべきだと述べており、mod_rewriteは最後の手段として扱っています。

.htaccessのリダイレクトが機能しないのはなぜですか。

ほとんどの場合、5つの原因で説明できます。AllowOverrideがNoneになっていてファイルが完全に無視されている、ファイルがドキュメントルートにない、または名前を間違えている、mod_rewriteがロードされていない、ブラウザが以前の301をキャッシュしてサーバーに二度と問い合わせていない、あるいはルールが自分自身のターゲットにマッチしてループしている、のいずれかです。ブラウザではなくcurlでテストしましょう。キャッシュされた301のせいで、正しく直したルールが壊れているように見えてしまうからです。

.htaccessでの301リダイレクトを経てもクエリ文字列は残りますか。

RedirectとRedirectMatchでは残り、自動的に引き継がれます。RewriteRuleでは置換先の書き方によって結果が変わります。クエスチョンマークを含めなければ元のクエリ文字列がそのまま通過し、クエスチョンマークと自分のパラメータを書けばそれに置き換わり、末尾に何もつけないクエスチョンマークだけを置けば消去され、QSAフラグを使えば両方が結合されます。ここを間違えると、UTMパラメータが静かに失われます。

HTTPからHTTPSへ、non-wwwからwwwへのリダイレクトを1回のホップでまとめるにはどうすればよいですか。

ORで結んだ2つの条件を持つ1つのルールを使い、正規のスキームとホストへの書き換えを1段階で行います。2つの別々のルールブロックを使うと、wwwなしのhttp://で訪れた人には2回のリダイレクトが発生し、余分な一段ごとにレイテンシがかかり、信号も弱まります。プロキシやCDNの背後では、%{HTTPS}ではなく%{HTTP:X-Forwarded-Proto}を検査してください。そうしないとループを作ってしまいます。

.htaccessはサイトを遅くしますか。

わずかに、そして避けられない形で遅くなります。Apacheのドキュメントもそれをはっきりと述べています。.htaccessファイルを許可すると、実際に使っているかどうかにかかわらずパフォーマンスへの影響が生じます。httpdがリクエストごとにすべてのディレクトリでファイルを探しにいくからです。メインのサーバー設定にアクセスできるなら、同じルールはそちらに置いて、起動時に一度だけ読み込ませる方が適切です。

Elidoを試す

URLを貼り付けて短縮リンクを取得

登録不要。リンクは30日間有効。永久に保存するには登録してください。

Free、登録不要 · 1日あたり2件

Elidoを試す

EUホスティングのURL短縮サービス。カスタムドメイン、詳細な分析、オープンAPI付き。無料プラン - クレジットカード不要。

タグ
htaccess 301 redirect
apache redirect
mod_rewrite
301 redirect
redirect https
url redirect

続きを読む