1分で読了エンジニアリング

リダイレクトループとは:ERR_TOO_MANY_REDIRECTSの見つけ方と直し方

リダイレクトループは、ブラウザが2つのURLの間を行き来し続け、やがて諦めてしまう現象だ。1つのコマンドでリダイレクトチェーンを追跡し、食い違っているルールを特定して、根本的に修正しよう。

Marius Voß
DevRel · edge infra
互いを指し合う2つのURLとして描かれたリダイレクトループ。ブラウザのホップカウンターが上限に達しERR_TOO_MANY_REDIRECTSを返す様子

リダイレクトループとは、決して着地しないHTTPリダイレクトの連鎖だ。URL AがブラウザをURL Bへ送り、BがそれをAへ送り返し、およそ20ホップの後にブラウザは試みるのをやめてERR_TOO_MANY_REDIRECTSを表示する。**遷移先のページ自体は何も壊れていない。**2つのルールが、そのURLが本来どこに属するべきかについて単に食い違っているだけであり、それぞれが相手の結果を打ち消し続けているのだ。

ループするリダイレクトは、正しい場所さえ見れば自ら原因を名乗ってくれる数少ないWebエラーの1つだ。見るべきはブラウザでも、エラーページでも、ましてやキャッシュでもない。リダイレクトチェーンだ。すべてのホップはLocationヘッダーを伴っており、繰り返し現れる2つのアドレスこそが、突き合わせるべき2つのルールにほかならない。本稿では、1つのコマンドによる診断、私がこれまで解きほぐしてきたほぼすべてのリダイレクトサイクルの背後にある原因、そしてどこか別の場所にひっそりと第2のループを生み出さない修正方法を順に見ていく。リダイレクトという概念自体があいまいなら、リダイレクトの種類が地図になる。本稿はそのトラブルシューティング版だ。

ERR_TOO_MANY_REDIRECTSが本当に意味すること

ブラウザは代わりにリダイレクトをたどってくれるが、永遠にではない。各クライアントはホップ数の予算を持っており、それを使い切るとリクエストを放棄する。Chromeは20回で停止し、変更する手段はない。Firefoxはnetwork.http.redirection-limitという同じデフォルト値を公開している。curlは文句を言い出すまでに50回まで許容する。規格はその数値をあえて定めていない。RFC 9110は、クライアントが循環的なリダイレクトを検出して介入すべきだと述べているだけであり、これはつまり、どのブラウザにもサーキットブレーカーが必要だということを仕様書なりの言い回しで述べているにすぎない。

この違いは診断において重要だ。なぜなら、そのエラーはそもそも循環が存在することを証明してはいないからだ。重複のない21個の異なるホップからなるチェーンも、2つのURLが永遠に行き来するのとまったく同じメッセージを引き起こす。どちらも修正する価値はあるが、突き合わせるべきアドレスのペアを持っているのは一方だけだ。MDNのリダイレクトに関するリファレンスは両方の形を扱っており、実務上の違いはチェーンを出力した瞬間に明らかになる。

HTTPSのURLとHTTPのURLの間で発生するリダイレクトループ。ブラウザのホップカウンターが上限に達しERR_TOO_MANY_REDIRECTSを返す様子

エラーページが隠しているもう1つの事実がある。恒久的なリダイレクトはブラウザにキャッシュされるということだ。そのループが301レスポンスから作られていた場合、サーバー側を修正した後も、そのキャッシュエントリが期限切れになるか訪問者自身が消去するまで、訪問者はローカルでループし続ける。この1つの事実こそが、私がリダイレクトの変更をまず302でテストし、チェーンが正しいと確認できてから初めて301へ昇格させる理由であり、その習慣については301 vs 302リダイレクトが全体像を説明している。

1つのコマンドでリダイレクトチェーンを追跡する

ブラウザは飛ばそう。どんなリダイレクトループであっても、最も速く読み解けるのはターミナルだ。curlは各ホップを1つのエラーページへまとめてしまうことなく、そのまま表示してくれるからだ。

curl -sIL https://example.com | grep -E '^HTTP|^[Ll]ocation'

ホップごとにステータス行とLocationヘッダーが順番に得られる。上から下へ読んでいくと、3つのパターンのいずれかが現れる。2つのURLが交互に現れるなら本物の循環であり、そのペアが問題のある2つのルールを名指ししている。異なるURLが延々と続くなら、誰かが長年かけて積み重ねてきたチェーンであり、各ホップはそれ単体では正当だ。そして、ターミナルでは問題なく解決するのにブラウザでは失敗し続けるなら、curlはデフォルトでCookieを送らないため、そのループはCookieが原因だということになる。

最後のケースは個別に確認する価値がある。人々が午後いっぱいDNSを疑い続ける原因になるのが、まさにこのケースだからだ。

curl -sIL -c jar.txt -b jar.txt https://example.com/account | grep -E '^HTTP|^[Ll]ocation'

Cookieジャーを添えれば、ログインや同意まわりのループはコマンドライン上で再現でき、どのエンドポイントがセッションを設定しては拒否しているのかを実際に確認できる。ブラウザで同等の見え方をするのは、「Preserve log」を有効にしたNetworkパネルであり、これによってページが遷移しても以前のホップが消去されずに残る。ターミナルを開きたくないなら、私たちのリンクチェッカーがブラウザ内でチェーンを追跡してくれ、すべてのホップでステータスコードを表示する。

ほぼすべてのリダイレクトサイクルの背後にある原因

チェーンさえ見えてしまえば、原因はたいてい少数のパターンのどれかに絞られる。繰り返し現れるURLのペアが、どの層を見るべきかを教えてくれる。スキームが行ったり来たりしているならTLS終端を、ホスト名が入れ替わっているなら正規ホストルールを、パスがスラッシュを付けたり外したりを繰り返しているならリライトルールの順序を疑おう。

TLSを終端するプロキシの背後にあるHTTPSルール

これは現代のWebにおける最も一般的な無限リダイレクトであり、プロキシがオリジンの手前でTLSを終端し始めた瞬間から存在してきた。プロキシは訪問者からのHTTPSリクエストを受け取り、それからオリジンへは平文のHTTPで取得しに行く。オリジンは安全でないリクエストを目にし、指示されたとおりにHTTPSへリダイレクトする。プロキシはそのリダイレクトを訪問者に返し、再びリクエストされ、再びオリジンをHTTPで取得しに行き、そしてこれが延々と繰り返される。Cloudflareはこの失敗パターンをまさにflexible暗号化モードのもとで文書化しており、他のあらゆるプロキシも名前こそ違え同じ罠を抱えている。

明快な修正方法は2つある。1つは、プロキシをfull暗号化モードに切り替えてオリジンとTLSで通信させることで、ほとんどのケースで正解となる。もう1つは、オリジンへのホップがどうしても平文のままである必要がある場合に、オリジン側のルールが生の接続ではなくX-Forwarded-Protoヘッダーを読むようにし、エッジですでに安全だったリクエストをリダイレクトしないようにすることだ。

wwwとapexがどちらを優先するかで食い違っている

正規ホストルールが1つあるだけなら問題ない。だが、それが違う時期に違う人によって書かれた2つになると、ループになる。典型的なパターンでは、サーバー設定がapexをwwwへ送る一方で、アプリケーション設定はwwwをapexへ送り返しており、しかもどちらも自分が正しいと確信している。チェーンを見れば一目瞭然だ。example.comからwww.example.com、またexample.comへと、永遠に続く。

ホストをどちらか1つ選び、それをただ1か所だけで強制し、両者を一致させようとするのではなくもう一方のルールを削除しよう。これは末尾スラッシュや小文字化のリライトにも当てはまる。2つのルールが同じURLを互いに逆方向へ正規化していると、それぞれのルール単体は健全であってもリダイレクトサイクルが生まれてしまう。

もはや一致しなくなったCMSやアプリのURL設定

ほとんどのアプリケーションは自身の正規アドレスを保存しており、そのフィールドは親しみやすい名前を持ったリダイレクトルールにほかならない。ドメインを変更したり、新しい環境へ移行したり、別のホストからデータベースのスナップショットを復元したりすると、アプリはすべてのリクエストを、それ自身がまた戻ってくるようなアドレスへリダイレクトし始める。この設定はWebサーバーの設定ファイルではなくデータベースに存在しているため、先ほど実施した設定監査を難なくすり抜けてしまい、それが見つけにくさの原因になっている。

その特徴は、現在のホスト名から離れたきり二度と戻ってこないチェーンだ。保存されているアドレスを実際に配信しているドメインへ修正し、間違ったアドレスをキャプチャしてしまったアプリケーションキャッシュやページキャッシュをすべて破棄しよう。

ログイン済みユーザーだけに影響するCookieとセッションのループ

ここではルールは無関係で、状態こそが問題だ。ゲートは未認証の訪問者をログインページへリダイレクトし、ログインページは認証済みの訪問者を送り返すが、遷移先のドメインで読み取れないセッションCookieのせいで、双方とも相手が処理すべきだと思い込んだままになる。同意バナーも、同意Cookieを設定するリダイレクト自体がブロックされている場合に、同じ形のループを引き起こす。

見分け方は前のセクションと同じだ。まっさらなプライベートウィンドウでは動作する、あるいはCookieジャーを使わないcurlは正常に解決する。CookieのdomainPathのスコープ、実際に配信しているスキームに対するSecureSameSite属性、そしてapexで設定されつつwwwで読まれていないかを確認しよう。

チェーンの中で繰り返されるもの考えられる原因最初に変更すべきこと
httphttpsを行き来するプロキシがTLSを終端し、オリジンが譲らないfull暗号化モードにするか、XFPを信頼する
apexとwwwを行き来する2つの正規ホストルールが存在する片方を削除し、ルールを1つだけ残す
パスがスラッシュを付けたり外したりするリライトルールの順序が誤っているルーティングの前に一度だけ正規化する
ホスト名から離れたきり戻らない保存されたサイトアドレスが古いアプリのURL設定を修正し、キャッシュを破棄する

リダイレクトループは、いったんチェーンが画面に表示されてしまえばほとんど謎ではなくなる。しかし、追跡せずに推測に頼ると、午後を丸ごと費やしてしまう。リダイレクト層と言い争うより、それを自分の手で管理したいなら、Elidoの無料プランは、遷移先が付け替え可能な単一の保存された値であり、すべてのホップが記録されるリンクを提供する。

第2のループを作らずに直す

修正そのものは短く、構文よりも順序の方が重要だ。**1つのルールを変更したら、再び追跡すること。**3つのルールを変更してから再読み込みしても、どれが効いたのかはまったくわからない。あるチームが、最初の試みですでに直していたルールが原因のループに、そのやり方で1時間も費やしてしまうのを私は見たことがある。

  1. チェーンが名指しした2つのルールのうち、正確に1つだけを削除または反転させ、リクエストが1ホップで200に到達できるようにする。
  2. curl -sILによる追跡を再実行し、チェーンがせいぜい1回のリダイレクトになっており、ホスト名の繰り返しがないことを確認する。
  3. ブラウザのキャッシュを消去するか、プライベートウィンドウでテストする。以前配信した301はまだローカルにキャッシュされており、もう存在しない失敗を偽って再現してしまうからだ。
  4. チェーンの形が確定してから、初めて一時的なリダイレクトを恒久的なものへ昇格させる。

このリストの最後には2つの落とし穴が待っている。1つはHSTSだ。あるホストが一度Strict-Transport-Securityヘッダーを送ってしまうと、ブラウザはそれ以降すべてのリクエストを自らHTTPSへ昇格させるため、HTTPSを強制するオリジン側のルールは冗長になり、HSTSのエントリを消去しない限り再現できないループへとプロキシの設定ミスを変えてしまうことがある。もう1つはループの手前にあるキャッシュだ。301をキャッシュしたCDNは、オリジンがそれを送らなくなった後も平然と配信し続けるため、パージは修正の後ではなく修正の一部として行うべきだ。ループしていない長いチェーンも、同じタイミングで整理する価値がある。ホップが増えるたびにクエリ文字列が失われる機会も増え、これがまさにUTMパラメータがGA4で消える理由であり、短縮リンクが1ホップにとどまっている限りSEOを損なう必要はない理由の一部でもある。

リダイレクトチェーンのビフォーアフター:ホスト間でループする4ホップのチェーンと、同じリクエストが単一の正規リダイレクトで解決される様子

ループが短縮リンクで起きている場合

短縮リンクは循環が生まれる場所をもう1つ増やすが、それはシャートナー自体のリダイレクトではない。短縮リンクは1つの保存されたホップだ。スラッグが入り、遷移先が出る。ループが現れるのは、遷移先が戻ってきてしまうときであり、これは思われているよりも頻繁に起こる。誰かがキャンペーンリンクを編集してランディングページを指すようにするが、そのランディングページには、前四半期には正規の共有アドレスだったという理由でその短縮URLへリダイレクトする古いルールが残っており、今や両者が跳ね返り合う。どちらのホップも、まさに設定どおりに動作しているのだ。

同じ形のチェーンには、もう2つのバリエーションが現れる。1つは、一括編集の後に互いを指し合うようになった短縮リンクのペアで、たいていは遷移先の列に最終的なURLではなく短縮URLが入ったスプレッドシートのインポートに由来する。もう1つは、シャートナーへ戻ってリダイレクトするホストへ今も解決し続けているカスタムドメインで、これはリンクの問題というよりDNSの取り残しであり、短縮リンクのカスタムドメインがレコードのあるべき姿を扱っている。この3つのいずれの場合も、修正は遷移先を別のリダイレクトではなく最終的なページに設定することであり、すでに印刷または公開されているものには一切手を触れずに行える。

遷移先はURLに焼き込まれているのではなく保存されているため、これらのどれについても再印刷は必要ない。それこそが管理されたリンクを使う理由のすべてであり、症状がループに限らない場合には短縮リンクが動作しないがより広いトリアージガイドになる。

次の1件を出荷させない

リダイレクトループは設定のドリフトによるバグであり、だからこそ長続きする修正は地味なものになる。正規ホストの決定は1か所にとどめ、スキームやホスト名に触れる2つ目のルールを見つけたら、それを見た瞬間にバグとして扱おう。新しいリダイレクトは、誰かが真っ白なページを報告してくる前に、公開する前にcurl -sILで追跡すること。リンクが収益に関わるなら、そこにチェックを仕掛けておこう。チェーンが1ホップを超えて伸びたら失敗する定期実行の追跡は、顧客より先にドリフトを捉えてくれる。リダイレクトの監視は、それを実際のアラートに組み込むとどうなるかを示している。

より広い習慣は、遷移先を監査可能なデータとして扱うことだ。リンク切れ防止は、静かに解決しなくなるリンクに対する同じ規律を扱っており、いずれにせよ同じ週次チェックで済む。正直なところ、私がこれまで見てきたループのほとんどは、有能な2人がそれぞれ違う層で同じ問題を、1か月の間隔を空けて修正した結果として生まれたものだった。ルールを一度書き留めておけば、ループはそもそも起こりえなくなる。

コーナーストーン記事を読む

本稿はengineeringクラスターに属する。リダイレクトの経路そのものの形については、リダイレクトのp95を15ms未満にするが行儀の良い1ホップのコストを扱っており、ルールを積み重ね始める前に、リダイレクトの種類がどのステータスコードをどこで使うべきかを扱っている。

ブログ内の関連記事

よくある質問

ERR_TOO_MANY_REDIRECTSとは何を意味するのですか。

ブラウザが実際のページにたどり着くことなくリダイレクトを次々とたどり続け、ホップ数の上限に達して停止したことを意味する。ページ自体は通常問題ない。2つのリダイレクトルールがそのURLの帰属先について食い違っており、互いに相手の結果を打ち消し合っているのだ。Chromeは ERR_TOO_MANY_REDIRECTS を表示し、Firefoxはページが正しくリダイレクトされていないと表示し、Safariはリダイレクトが多すぎると報告する。

ERR_TOO_MANY_REDIRECTSはどうすれば直りますか。

まずチェーンを追跡し、そのURLをめぐって争っている2つのルールのどちらかを取り除こう。そのアドレスに対して curl -sIL を実行し、すべての Location ヘッダーを読むこと。繰り返し現れるURLのペアが、どちらのルールを削除または反転すべきかを教えてくれる。よくある原因は、TLSを終端するプロキシの背後で動くHTTPSルール、別のwwwルールの上に重なったwwwルール、そして配信中のドメインともはや一致しないサイトアドレス設定だ。

ブラウザは諦めるまでにいくつのリダイレクトをたどるのですか。

ブラウザによって異なるが、おおよそ20回だ。Chromeは20ホップで停止し、その上限は変更できない。Firefoxは同じ上限を network.http.redirection-limit として公開しており、デフォルト値は20だ。curlは --max-redirs を変更しない限り最大50回まで追跡する。仕様は具体的な数値を定めていない。RFC 9110は、クライアントが循環的なリダイレクトを検出して介入すべきだと述べているだけであり、そのため各クライアントが独自の上限を決めている。

Cookieを消去すればリダイレクトループは直りますか。

場合によっては直るが、それ自体が手がかりになる。プライベートウィンドウでページが問題なく読み込まれるなら、ループは古くなったセッションや同意Cookieが原因であり、サーバー側のルールの問題ではない。その場合、Cookieを消去することはその訪問者にとって実際に有効な修正となる。新しいプライベートウィンドウでもループが発生するなら、Cookieは無関係であり、問題はリダイレクトルール、プロキシの設定、あるいはCMSのURLフィールドのいずれかにある。

HTTPSやCDNプロキシを有効にしたらサイトがループし始めたのはなぜですか。

2つの層がどちらもHTTPSを要求しているのに、そのうち一方はオリジンへ平文のHTTPで通信しているからだ。プロキシはポート80でオリジンにリクエストし、オリジンのルールがそれをHTTPSへ送り返し、プロキシは同じやり方でそのリクエストに応答し、サイクルが終わらなくなる。プロキシの暗号化モードをフル(full)に切り替えてオリジンへTLSで接続するようにするか、ルールが生の接続ではなく X-Forwarded-Proto ヘッダーを信頼するようにしよう。

短縮リンクがリダイレクトループを引き起こすことはありますか。

ある。遷移先が短縮リンク自身へ戻ってくる場合や、2つのリンクが互いを指し合っている場合だ。よくあるのは、その短縮URLへリダイレクトするページを遷移先に設定するようリンクを編集してしまうケースで、両方のホップがまさに設定どおりに動作しているため、ブラウザを何度更新してもループは解消しない。遷移先を別のリダイレクトではなく最終的なページに設定すれば、何も再印刷することなくループは消える。

Elidoを試す

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

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

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

Elidoを試す

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

タグ
redirect loop
err_too_many_redirects
too many redirects
redirect chain
infinite redirect
301 redirect loop

続きを読む