リダイレクトが繰り返されるときの原因と直し方|ERR_TOO_MANY_REDIRECTS の切り分け
ページを開こうとしたら、「このページは動作していません」「example.com でリダイレクトが繰り返し行われました」という画面が出て、先に進めない。Chrome では、エラーコードとして ERR_TOO_MANY_REDIRECTS が表示されます。「307 エラーが出た」という相談の多くは、実際にはこの状態を指しています。
画面に出ているのはエラーですが、その元になっているのは転送が終わらずに続いていることです。一度の 307 や 301 は正常な案内でも、行き先が元の場所へ戻る循環ができてしまうと、ブラウザーは途中で通信を打ち切ります。原因は、転送を返している番号ではなく、複数の設定の組み合わせにあります。
症状
転送が循環して、ブラウザーが途中で通信を打ち切ります。
切り分け
curl で転送の連鎖を一覧にすると、循環の位置が分かります。
主な原因
http と https の食い違い、www の有無、CMS の URL 設定、プロキシです。
仕様上の位置づけ
RFC 9110 は、クライアントが循環を検出して介入すべきとしています。
原因が分かりにくいと、時間を失う
リダイレクトループの厄介な点は、原因が一つの設定に収まらないことです。
転送の設定は、さまざまな場所に分散しています。Web サーバーの設定、アプリケーション(CMS やフレームワーク)の設定、CDN やロードバランサー、ブラウザーに残った HSTS の記憶。どれか一つだけを見ても、設定は正しく見えることがあります。ところが、組み合わせると「A が B へ、B が A へ」という循環ができてしまいます。
さらに、自分の環境では再現して、他の環境では再現しないことがあります。ブラウザーに保存されたキャッシュや Cookie、HSTS の状態が絡むためです。「直ったと思ったのに、まだ表示されない」という場面が頻発します。
また、運営者にとっては、検索エンジンのクローラーにも影響が出る点が見過ごせません。Google のクロールの文書によれば、Google のクローラーは既定で最大 10 回まで転送を追いかけます。それを超えると、転送の失敗として扱われます。ループが長く放置されると、ページが正しく処理されない状態が続きます。
こうした事情から、場当たり的に設定を変えるのではなく、手順を決めて切り分けることが、時間の節約につながります。
ブラウザーはなぜ打ち切るのか
そもそも、転送は何回まで追うのでしょうか。
WHATWG の Fetch Standard は、ブラウザーの通信の手順を定めています。リダイレクトを処理するたびに「転送回数」を数え、回数がすでに 20 に達していたら、ネットワークエラーとして終了します。この上限を超える連鎖は、事実上、ループとみなされます。
RFC 9110 も、クライアントは循環するリダイレクトを検出して介入すべき(SHOULD)としています。人間が目で確かめなくても、ソフトウェアが自動で止めるべきだ、という考え方です。
ほかのツールでは、上限が異なります。curl の場合、--max-redirs の既定値は 50 回です。同じ循環でも、ブラウザーは 20 回で、curl は 50 回で止まります。ツールごとに数が違うことは、切り分けのときに知っておくと便利です。
ループの代表的な原因
実際の現場で多い原因を挙げます。
原因 1: http と https が互いに転送している
もっとも多い原因です。サーバーは「http を https へ転送」し、アプリケーションは「https を http へ転送」している、という状態です。
リバースプロキシや CDN を使うと、さらに起きやすくなります。利用者はプロキシまで https で通信し、プロキシから Web サーバーへは http で通信する構成です。Web サーバー側には、すべてが http に見えるため、「http だから https へ転送しよう」と判断します。ところが、転送先の https もプロキシ経由で、再び Web サーバーには http として届くため、転送が止まりません。
clientserver
原因 2: www の有無が食い違っている
Web サーバーが「www なし」へ正規化し、アプリケーションが「www あり」へ正規化している場合です。お互いに、相手の URL を誤りとみなして転送し合います。
原因 3: アプリケーションの URL 設定とサーバー設定の不一致
CMS やフレームワークには、サイトの URL を登録する設定があります。ここで登録した URL(http か https か、www の有無、末尾のスラッシュ)と、サーバー側の転送設定が食い違うと、循環が起きます。サイトを https に移行した直後や、ドメインを変更した直後に頻発します。
原因 4: HSTS の記憶とサーバーの設定の衝突
サーバーが https から http へ転送する設定を持ったまま、ブラウザーが HSTS を記憶していると、ブラウザーは http を https に書き換え、サーバーは https を http に戻す、という往復になります。307 Internal Redirect と HSTSで説明したとおり、HSTS はブラウザー内で書き換えを行うため、サーバーのログには現れません。
原因 5: 認証まわりの転送と Cookie
未ログインの利用者をログインページへ転送し、ログイン後に元のページへ戻す仕組みで、Cookie が保存されないとループになります。Cookie がブラウザーの設定で拒否されている場合や、Cookie のドメインや Secure 属性が実際の URL と合っていない場合に起こります。ログイン状態を示す Cookie が届かないため、毎回「未ログイン」と判断されて、ログインページへ戻されるのです。
原因 6: 転送ルールそのものの書き間違い
正規表現が広すぎて、転送先の URL も転送の対象に含んでしまう場合があります。たとえば「/blog/ で始まる URL を /blog/ 配下へ転送する」ような規則は、転送先が再び同じ規則に当てはまるため、永久に続きます。
手順を決めて切り分ける
原因が多岐にわたっても、切り分けの手順は共通です。
手順 1: curl で転送の連鎖を見る
ブラウザーを使わずに、転送の連鎖を確認します。-I でヘッダーだけを取得し、-L で転送を追い、--max-redirs で回数を絞ると、循環を短く再現できます。
curl -sIL --max-redirs 8 https://example.com/ | grep -iE '^(HTTP|location)'
出力例のように、同じ URL が繰り返し並んでいたら、そこが循環の中心です。
HTTP/2 301
location: http://example.com/
HTTP/1.1 301
location: https://example.com/
HTTP/2 301
location: http://example.com/
この例では、https と http のあいだで転送が往復しています。どの URL がどの URL を指しているのかが、一目で分かります。
回数だけを確認したい場合は、次のコマンドが使えます。
curl -sL --max-redirs 20 -o /dev/null -w '%{num_redirects} %{url_effective}\n' https://example.com/
手順 2: 転送を返しているのが誰かを特定する
連鎖の各段階で、どこが転送を返したのかを調べます。同じ URL でも、Web サーバー、アプリケーション、CDN のどこが返したかで、直す場所が変わります。応答ヘッダーの Server や Via、X-Powered-By などが手がかりになります。
手順 3: ブラウザーの状態を切り分ける
curl では再現しないのに、ブラウザーでは起きる場合は、ブラウザー側の状態が原因です。シークレットウィンドウで開く、Cookie を削除する、HSTS の記憶を確認する、といった確認をします。Chromium 系のブラウザーでは、chrome://net-internals/#hsts でドメインの HSTS の状態を調べられます。
手順 4: 開発者ツールで、ログを残して見る
開発者ツールのネットワーク画面で「ログを保持」を有効にすると、転送ごとにページ遷移でログが消えず、連鎖の全体が確認できます。各行の Location ヘッダーを追うと、循環の経路が分かります。
原因別の直し方
http と https の食い違い(プロキシがある場合)
プロキシが付けてくれる X-Forwarded-Proto ヘッダーを使い、利用者が実際に使った通信方式で判断します。Web サーバーが見ている通信方式ではなく、元の通信方式を基準にします。nginx の例を示します。
server {
listen 80;
server_name example.com;
# プロキシ経由で、利用者が http でアクセスしてきたときだけ https へ転送する
if ($http_x_forwarded_proto = "http") {
return 308 https://$host$request_uri;
}
# ... 以降は通常のサイト設定
}
アプリケーション側にも、プロキシの背後にいることを伝える設定(信頼するプロキシの指定)があります。フレームワークごとの設定名は異なるため、お使いのものの説明を確認してください。
www の有無の食い違い
正規化は、一か所だけで行います。Web サーバーで行うなら、アプリケーション側の正規化は無効にします。逆も同様です。どちらで行うかを決めたら、サイトの URL 設定も合わせます。
転送ルールの書き間違い
規則が転送先に当てはまらないかを確認します。条件を足して、すでに目的のパスにいる場合は転送しないようにします。
Cookie まわり
Cookie のドメイン、パス、Secure 属性が、実際の URL と合っているかを確認します。https でしか送られない設定(Secure)の Cookie を、http のページで期待していないかも見直します。
直したあとの確認
修正したら、次の 3 点を確認します。
curl -sILで、転送が 1 回、あるいは 0 回で最終ページに到達すること。- 想定した最終 URL に着いていること(
url_effectiveで確認)。 - シークレットウィンドウで、ブラウザーからも開けること。
恒久的な転送(301 や 308)を誤って公開していた場合は、ブラウザーが転送を保存していることがあり、修正しても古い転送が残ることがあります。その場合は、ブラウザーのキャッシュを消して確認してください。
再発を防ぐ設計の考え方
一度直しても、設定変更のたびに再発しては意味がありません。ループを起こしにくくする考え方を、4 つ挙げます。
- 正規の URL を一つ決める。 「https で、www なし、末尾のスラッシュなし」のように、サイトの正規の形を文書で決めておきます。迷ったとき、設定の食い違いを判断する基準になります。
- 転送は一か所で行う。 Web サーバー、アプリケーション、CDN のうち、どこで転送するかを決め、他では行いません。複数の場所に同じ役割の設定があると、食い違いの温床になります。
- 旧 URL から最終 URL まで、転送は一度だけにする。 「http の www なし → https の www なし → https の www あり」のように連なる転送は、一度で最終地点に着くように設計し直します。表示も速くなり、循環の危険も減ります。
- 変更のたびに curl で確認する。 サイトの URL、証明書、CDN、プロキシの設定を変えたら、
curl -sILで転送の連鎖を必ず確認します。確認のコマンドを手順書に載せておくと、担当者が替わっても同じ確認ができます。確認の結果は、変更の記録とあわせて残しておくと、後から不具合が出たときに、どの変更が原因かをたどりやすくなります。
とくに、サイトの https 移行やドメインの変更といった大きな作業のあとは、ループが生まれやすい時期です。公開直後には、必ず複数の入り口(http と https、www の有無、末尾のスラッシュ)から順にアクセスして、すべてが一度の転送で正規の URL に着くことを確かめておくと安心です。スマートフォンなど、普段と異なる環境からも一度開いてみると、Cookie や記憶の違いによる見落としを減らせます。
循環は「設定の組み合わせ」を見直す合図
要点を整理します。
- ループの画面は、転送が終わらないために出るエラーで、307 や 301 の番号そのものが悪いわけではありません。
- ブラウザーは転送回数が 20 に達すると打ち切ります。curl の既定は 50、Google のクローラーは既定で 10 回までです。
- 原因は、http と https、www の有無、アプリケーションの URL 設定、プロキシ、HSTS、Cookie などの組み合わせにあります。
- 切り分けは、curl で連鎖を見る → 返している主体を特定する → ブラウザーの状態を除く、の順に進めます。
- 正規化は、一か所に集約します。
転送の番号ごとの意味をあらためて確認したい場合は、301・302・303・307・308 の違いが参考になります。