12k
All articles

「ERR_TOO_MANY_REDIRECTS」の修正方法

ERR_TOO_MANY_REDIRECTSを診断中心に解決。リダイレクトループ、HTTPとHTTPSの不一致、Cloudflare SSL、プロキシ設定を確認します。

OpenReplay Team
OpenReplay Team
「ERR_TOO_MANY_REDIRECTS」の修正方法

ERR_TOO_MANY_REDIRECTS とは、ブラウザがリダイレクトのチェーンを辿ったものの、最終的な解決に至らなかった状態を指します。最も一般的なパターンは「URL A → URL B → URL A」という循環で、ブラウザが内部のホップ数上限に達した時点で処理を中断します。

このエラーに遭遇した場合、おそらくプロキシや SSL の設定を変更した直後に、すべてのリクエストが2つの URL の間で延々と往復するようになったはずです。1時間前まで正常に動作していたにもかかわらず、見た目には何も壊れていないため、非常に厄介なエラーです。これはブラウザのバグでも一時的な障害でもなく、スタック上の2つ以上のリダイレクトルールが、URL の最終的な遷移先について矛盾していることを示しています。本ガイドでは診断を優先します。設定を変更する前にループを追跡し、真の根本原因を特定します。ほとんどの開発者にとって、その原因は WordPress プラグインではなく、プロキシや CDN の背後にあるプロトコルの不一致です。

重要なポイント

  • ERR_TOO_MANY_REDIRECTS はリダイレクトループです。Chromium と Firefox は20ホップ後、Safari はそれより早い段階で処理を中断し、ページの代わりにエラーを表示します。
  • 何かを変更する前に診断してください。curl -I -L https://yourdomain.com を実行すると各ホップのヘッダーが表示され、ループが発生している場合は連続する Location: 行に同じ2つの URL が繰り返し現れます。
  • 開発者が最もよく遭遇するループは、プロトコルの不一致です。プロキシや CDN が TLS を終端してプレーンな HTTP をオリジンに転送し、そのオリジンが HTTPS へ強制リダイレクトするため、サイクルが永遠に繰り返されます。
  • Cloudflare では、Flexible SSL と「Always Use HTTPS」(またはオリジン側が HTTPS を強制する設定)を組み合わせると必ずループが発生します。オリジン証明書をインストールした後、Full (strict) に切り替えてください。
  • 恒久的な修正は、HTTP→HTTPS および www/非 www のリダイレクトを、複数のレイヤーではなく、ちょうど1つのレイヤー(フレームワーク、Web サーバー、または CDN)で管理することです。

「ERR_TOO_MANY_REDIRECTS」とは何か?

リダイレクトループとは、サイトがある URL に対して別の URL へのリダイレクトを返し続け、最終的に元に戻ってくるため、ブラウザが最終的な 200 レスポンスに到達できない状態です。ブラウザにはホップ数の上限があります。Chromium と Firefox はともに20ホップで停止し、Safari はそれより早い段階で停止します。上限に達した時点でリクエストを中断し、エラーを表示します。

エラーメッセージはブラウザによって異なりますが、以下はすべて同じ状態を表しています。

ブラウザメッセージ
ChromeERR_TOO_MANY_REDIRECTS / “redirected you too many times”
Firefox”The page isn’t redirecting properly”
Edge”This page isn’t working right now”
Safari”Safari Can’t Open the Page”

リダイレクト自体は通常の 3xx レスポンスであり、多くの場合 301 または 302 で、それぞれ Location ヘッダーを含んでいます。ループが発生している場合、同じ2つの Location の値がブラウザが諦めるまで交互に現れ続けます。

最初に診断する:リダイレクトチェーンを追跡する

設定を変更する前に、コマンドラインからチェーンを追跡してください。curl -I -L https://yourdomain.com を実行すると各ホップのレスポンスヘッダーが表示され、ループが発生している場合は連続する Location: 行に同じ2つの URL が繰り返し現れます。curl のマニュアルによると、-I--head)はヘッダーのみを取得し、-L--location)は各 Location ヘッダーを辿って次の URL へ進みます。

ループしているオリジンは次のような出力を生成します。

HTTP/2 301
location: https://app.example.com/

HTTP/2 301
location: http://app.example.com/

HTTP/2 301
location: https://app.example.com/
...
curl: (47) Maximum (50) redirects followed

ここで http://https:// が交互に現れているのは、プロトコル不一致ループの典型的なパターンです。レスポンスボディも確認したい場合は curl -sSL -o /dev/null -D - https://yourdomain.com を使用してください。ボディを破棄しながらすべてのヘッダーを出力します。

インストール不要の代替手段として、ブラウザの DevTools の Network タブでも各 Location を含む 301/302 チェーンを確認できます。また、Redirect Path 拡張機能やオンラインのリダイレクトチェッカーを使えば、URL のチェーンを視覚的に確認できます。どの出力を使う場合でも、繰り返し現れる URL に注目してください。その繰り返しのペアがループの正体です。

開発者が最も遭遇する原因:SSL 終端による HTTP↔HTTPS ループ

開発者が最もよく遭遇するリダイレクトループの原因はプラグインではありません。プロトコルの不一致です。プロキシや CDN が TLS を終端してプレーンな HTTP をオリジンに転送し、アプリが http を検知して https にリダイレクトするため、サイクルが永遠に繰り返されます。ブラウザはエッジに対して HTTPS で通信し、エッジはアプリに HTTP で通信し、アプリは「親切に」HTTPS にリダイレクトします。

Cloudflare の場合、SSL/TLS を Flexible に設定しながらオリジンも HTTPS を強制すると必ずループが発生します。Flexible はオリジンに常に HTTP で送信するからです。よくあるトリガーは、Flexible の上に「Always Use HTTPS」トグルを重ねて有効にすることです。修正方法は、オリジンに証明書をインストールしてからモードを Full (strict) に切り替えることです。現在のモードセットは Off、Flexible、Full、Full (strict)、Strict であり、古いガイドに記載されている「3つのモード」ではありません。

独自のプロキシやロードバランサーの背後では、アプリが再リダイレクトするのではなく、転送されたプロトコルヘッダーを信頼するように設定してください。プロキシはブラウザの元のスキームを X-Forwarded-Proto として送信し、アプリはプレーンテキストのホップではなくそのヘッダーを読み取る必要があります。Express では trust proxy を有効化して、req.protocolreq.secure が転送された値を反映するようにします。

// Trust the first proxy hop, then req.secure reflects X-Forwarded-Proto
app.set('trust proxy', 1);

app.use((req, res, next) => {
  if (!req.secure) {
    return res.redirect(301, `https://${req.headers.host}${req.originalUrl}`);
  }
  next();
});

trust proxy を設定しないと、TLS を終端するプロキシの背後では req.secure が常に false のままとなり、このミドルウェアがループを引き起こします。Nginx でオリジンのリダイレクトを行う場合は、転送されたスキームを条件にガードを設けることで、すでに HTTPS として到達したトラフィックに対してルールが再度発火しないようにします。

# Only redirect when the edge saw plain HTTP
if ($http_x_forwarded_proto = "http") {
  return 301 https://$host$request_uri;
}

ログイン済みユーザーに対してのみ、または CDN の背後でのみ発生する本番環境のループは、匿名の curl 実行では検出できません。影響を受けたセッションのセッションリプレイを使えば、実際のユーザーのクッキーとエッジのコンテキストのもとで、どの2つの URL が往復していたかを確認でき、サーバーログが示しているものの直接確認できない状態を再現できます。

クッキーと認証ガードによるループ

古いクッキーと誤ったルーティングの認証処理が、2番目に多いカテゴリです。古いリダイレクト状態を保持するクッキーや、ブラウザがキャッシュした HSTS ポリシーによって、他のユーザーは正常にサイトを表示できているのに特定のクライアントだけがループに陥ることがあります。これが「通常のウィンドウでは失敗するのに、シークレットモードでは動作する」という症状の原因です。まず、該当ドメインのクッキーとサイトデータをクリアしてください。

プログラム的なバージョンとしては、認証ガードが自身のログインページでループするケースがあります。/login 自体が「未認証ユーザーを /login にリダイレクトする」ルールの対象になっている場合、アクセスのたびに /login に戻され続けます。ログインルートをガードの対象から除外してください。同様の問題は、ログインハンドラーが保護されたページにリダイレクトし、そのページのガードがセッションクッキーが設定されていないためにユーザーを即座に送り返す場合にも発生します(これは前述の req.secure の不一致によって Secure クッキーがプロキシの HTTP ホップ上で拒否される場合によく起こる副作用です)。

リダイレクトルールの誤り:2つのレイヤーの不一致

リダイレクトループはほぼ常に、正規の URL についてフレームワーク、Web サーバー、CDN がそれぞれ異なるルールを強制するという、2つのレイヤーの不一致が原因です。したがって、恒久的な修正は HTTP→HTTPS および www/非 www のリダイレクトをちょうど1つのレイヤーで管理し、他の2つのレイヤーからは重複を取り除くことです。典型的なケースとしては、あるレイヤーが www を強制し、別のレイヤーがそれを除去する場合、リダイレクト先が自身の条件に一致するルール、またはフレームワーク・ホスト・CDN にわたって同じリダイレクトが重複している場合などが挙げられます。

フレームワークは後付けではなく、れっきとしたリダイレクトレイヤーです。

  • Next.js では next.config.jsasync redirects() でリダイレクトを定義します。permanent: true308 を、false307 を発行します。なお、Next.js 16 以降では旧来の middleware ファイルの規約は Proxy(proxy.ts)に名称変更されました。残存する middleware.ts は Edge ランタイムのユースケースでは引き続き機能しますが、非推奨となっており将来のバージョンで削除される予定です。そのため、リダイレクトや認証ロジックは proxy.ts に移行してください。
  • Nginx では return 301 ディレクティブを使用します。前述のようにガードを設けて、プロキシの背後で再発火しないようにしてください。
  • Express ではミドルウェアを使用します。チェーン内に HTTPS リダイレクトのミドルウェアをちょうど1つだけ配置してください。
  • WordPress も同じパターンの一例です。WordPress AddressSite Address の設定の不一致は単なる2つのレイヤーの不一致であり、両方を一致させることで解決します。

サーバー側では、Apache が「request exceeded the limit of 10 internal redirects」という別のエラーを出力することもあります。この内部リライトの上限である10は、ブラウザの20ホップ上限とは別のものであり、ループがクライアントではなく .htaccess に存在することを示す有用な手がかりとなります。Cloudflare では、HTTP→HTTPS のリダイレクトを最新の Rules エンジンRedirect Rule に設定してください。Page Rules は現代の Rules エンジンに移行される形で段階的に廃止されています。

リダイレクトループを防ぐには?

ほとんどのループは設定変更によって引き起こされるため、リダイレクトの変更は慎重に行ってください。以下のチェックリストを参考にしてください。

  1. 各リダイレクトを管理するレイヤーを1つに決める。 HTTPS および www/非 www の正規化を CDN、Web サーバー、アプリのどこで行うかを決定し、他の2つのレイヤーから重複を取り除く。
  2. プロキシを信頼し、再リダイレクトしない。 TLS を終端するホップの背後では、HTTPS を盲目的に強制するのではなく X-Forwarded-Proto を読み取る。
  3. 変更のたびにチェーンを再追跡する。 HTTPS、ドメイン、URL 構造の変更後は必ず curl -I -L を対象 URL に対して実行し、単一の 200 で終了することを確認する。

リダイレクトループから抜け出す最速の方法は、常に推測ではなく追跡です。失敗している URL に対して curl -I -L を実行し、Location ヘッダーで往復している2つの URL を特定し、流れに逆らってリダイレクトしているレイヤー(多くの場合、HTTP をオリジンに渡しているのに、そのオリジンが HTTPS を要求しているプロキシ)を修正してください。

よくある質問

シークレットモードではリダイレクトループが解消されるのに、通常のブラウザウィンドウでは解消されないのはなぜですか?

シークレットモードは保存されたクッキーや HSTS ポリシーのキャッシュがない状態で開始されます。そのため、通常のブラウジングではループが発生するのにプライベートウィンドウでは消える場合、サーバーのルールではなくクライアント側の状態が原因です。古いリダイレクト状態を保持する古いクッキーや、設定が誤ったオリジンに対して HTTPS を強制するキャッシュ済みの HSTS ポリシーは、影響を受けたプロファイルのみをループさせ、他のユーザーは正常にサイトを表示できます。該当ドメインのクッキーとサイトデータをクリアし、HSTS が疑われる場合は chrome://net-internals/#hsts を確認してください。

リダイレクトループにおける Cloudflare の Flexible と Full (strict) SSL の違いは何ですか?

Flexible は Cloudflare からオリジンへ常にプレーンな HTTP で送信するため、オリジンが HTTP から HTTPS へ強制リダイレクトする場合、リクエストは永遠にループします。Full (strict) はオリジンへ HTTPS で送信し、そこで信頼できる証明書を検証するため、オリジンの期待に応えてループを解消します。まずオリジンに有効な証明書をインストールしてから、SSL/TLS モードを Flexible から Full (strict) に切り替えてください。Flexible に重ねて設定する「Always Use HTTPS」トグルがよくあるトリガーです。

同じ URL に対して curl とブラウザで異なるリダイレクト動作が見られるのはなぜですか?

curl はクッキー、HSTS ポリシーのキャッシュ、ログインセッションがない匿名クライアントとして動作するため、すべてのリクエストに適用されるサーバーや CDN のルールによるループしか再現できません。特定のクッキー、認証済みセッション、または特定の CDN エッジに依存するループは、匿名の curl トレースには現れません。そのような場合は、実際のユーザーのコンテキストを取得してください。影響を受けたセッションのブラウザ DevTools、またはそのユーザーのクッキーと認証状態のもとでどの2つの URL が往復していたかを示すセッションリプレイが有効です。

Next.js 16 の next.config.js で定義したリダイレクトは、Link や router.push を使ったクライアントサイドナビゲーションにも適用されますか?

Pages Router を使用している場合、next.config.js の redirects() 関数で定義されたリダイレクトは、Proxy ファイル(旧ミドルウェア)が存在してパスに一致しない限り、Link や router.push によるクライアントサイドルーティングには適用されません。next.config.js のリダイレクトはフルページロードと初期リクエストに対してサーバー上で実行されるため、クライアントサイドの遷移はそれらを迂回する可能性があります。Next.js 16 ではミドルウェアファイルの規約が Proxy(proxy.ts)に名称変更されました。残存する middleware.ts は移行する必要があります。そこに残されたリダイレクトや認証ロジックは動作しなくなる可能性があります。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.