502
アプリケーションの手前にいるプロキシが、代わりに返した答えです。アプリケーション自身の応答が来なかったか、途中で切れたか、HTTP として成立していなかったことを意味します。
同じ 3 桁でも、リクエストのどちら側にいるかで別々の問題になります。自分に当てはまるブロックを読んでください。
これが出ている理由
リクエストの段階ではなく、解析の段階で落ちています。プロキシの 502 ページは HTML なので、`res.json()` が `Unexpected token '<'` を投げ、スタックトレースは壊れたサーバーではなくあなたのパーサーを指します。サイト全体ではなく特定のエンドポイントだけに出る傾向があり——重い処理のもの、コールドスタートするコンテナの裏にあるもの、ちょうど誰かがデプロイしたもの——オリジンが再起動している間、2 回のページ読み込みの間に出たり消えたりします。
やること
ステータスより先に本文を信じるのをやめてください。解析の前に `res.ok` を見て `content-type` を読むこと。502 の本文は `text/html` で、あなたの API が返す本物のエラーはそうではありません。次に、失敗したリクエストをネットワークパネルで開いて応答ヘッダーを読みます。`server`、`via`、`cf-ray` が「誰が書いたか」を名指ししていて、それが問いのすべてです。再試行するのは冪等なリクエストだけにして、バックオフと上限を付けてください。再起動中のオリジンに再試行の嵐をぶつけると、10 秒の瞬断が障害になります。それと、CORS に午後を溶かさないこと。エラーページは `access-control-allow-origin` を持たないので、ブラウザは本当の失敗の上に CORS の失敗を重ねて報告します。CORS のメッセージは原因ではなく症状です。
これが出ている理由
プロキシがあなたのアプリケーションから使える答えを取れませんでした。nginx ならエラーログの 1 行がそのまま診断結果です。覚えておく価値があるのは数種類だけです。`connect() failed (111: Connection refused) while connecting to upstream` は、`proxy_pass` のアドレスで誰も待ち受けていないという意味。`upstream prematurely closed connection while reading response header from upstream` は、リクエストの途中でワーカーが死んだ——OOM Killer、同期ワーカーでの未処理例外、あるいはアプリケーションサーバー自身のタイムアウトより長かったリクエスト。`upstream sent too big header while reading response header from upstream` は、応答ヘッダーが `proxy_buffer_size` を超えたという意味で、これは肥大した `Set-Cookie` がやることです。
やること
何よりも先にプロキシのエラーログを読んでください。理由と upstream のアドレスの両方が書かれていて、その 2 つはどれだけアプリケーション側のログを増やすより速く答えを出します。次に両端が一致しているかを確認します。アプリケーションが実際にバインドしているアドレスとポートは何か、それは `proxy_pass` に書いてあるものか。`127.0.0.1:3000` で待ち受けているアプリケーションは、別のコンテナやネットワーク名前空間にいるプロキシからは届きません。それはクラッシュした行程と同じ「拒否」として現れます。ログが早すぎる切断を指しているならアプリケーション自身のリクエストタイムアウトとメモリ上限を、ヘッダーが大きすぎると言っているなら `proxy_buffer_size` を上げたうえで、なぜヘッダーが膨らんだのかを調べてください。Cloudflare なら、エラーページに Cloudflare のブランドが付いているかを見ます。Cloudflare は、ブランド付きのページは Cloudflare が生成したもの、そうでないものはオリジンが返したものを中継しただけ、と文書化しています。
これが出ている理由
あなたの側は何も壊れていませんし、手元で何をしても変わりません。502 はそのサイト自身のマシン同士が話せなかったという意味なので、いま読んでいるメッセージは、サイトが応答できない間にその手前にいる層が書いたものです。たいていは数秒から数分、デプロイやクラッシュの最中に起き、サイトの一部だけに出ることもよくあります——トップページは普通に開くのに決済だけ落ちる、という形です。
やること
1 分待ってから再読み込みしてください。Cookie を消す、ブラウザを変える、DNS をフラッシュする、ルーターを再起動する——ここではどれも無駄骨です。失敗しているのは接続の向こう側だからです。数分以上続くなら報告する価値があり、そのとき役に立つのは正確な時刻と、エラーページに出ている `cf-ray` の値やリクエスト ID です。それがサイトの運用担当が実際に検索できる文字列です。有料のサービスなら、ステータスページを見るのが「もう気づいているか」を知る一番速い方法です。
失敗した URL に対してそのまま実行してください。エラーページを表示せずにステータスだけを出すので、ブラウザが描画したものではなくサーバーが言ったことが見えます。
curl -sS -o /dev/null -D - -w '\n%{http_code} in %{time_total}s\n' https://example.com/api/`-sS` は進捗表示を消しつつエラーメッセージは残し、`-o /dev/null` は HTML のエラーページを捨て、`-D -` は応答ヘッダーを標準出力に出し、`-w` は転送が終わったあとにステータスと実時間を出します。この出力の中の 3 つが、次にどこを見るかを決めます。ステータス行は、それが本当に 502 であって、アプリケーション自身が返した 500 ではないことを確定させます。ヘッダーの塊は書き手を名指しします——`via` の無い `server: nginx` は自前のプロキシ、`cf-ray` を伴う `server: cloudflare` は Cloudflare で、その `cf-ray` の値はサポートが聞いてくる ID です。そして経過時間が 504 との決め手になります。拒否された、あるいは壊れた upstream はミリ秒で返り、時間切れのほうはタイマーどおりに返る——nginx の既定の `proxy_read_timeout` なら 60 秒、Cloudflare が自前の 524 を出すまでなら 125 秒です。オリジンではなくプロキシ側の問題だと示すには、同じコマンドに `--resolve example.com:443:203.0.113.10` を付けて、元のホスト名と SNI を送りながら指定したオリジンのアドレスへ直接つないでください。
RFC 9110 §15.6.3 は 502 を、ゲートウェイまたはプロキシとして動作しているサーバーが、リクエストを果たすためにアクセスした内側のサーバーから不正な応答を受け取ったときに返すステータス、と定義しています。この書きぶりから 2 つのことが導かれ、どちらも「どこを見るか」を変えます。1 つ目、このコードを書いているのは中間にいる側——nginx、HAProxy、Envoy、ロードバランサー、Cloudflare——であって、あなたのアプリケーションではありません。アプリケーションは一度も動いていない可能性すらあります。2 つ目、「不正な応答」は「エラー応答」よりずっと広い括りです。TCP 接続の拒否、応答ヘッダーが揃う前の切断、プロキシが解釈できないステータス行、プロキシのバッファを超える大きさのヘッダー——これらは全部あてはまります。一方、アプリケーションが自分で 500 を返しきった場合はあてはまりません。それは正しい応答なので、プロキシはそのまま素通しします。502 はタイムアウトでもありません。RFC 9110 §15.6.5 は「時間内に応答が来なかった」ほうを 504 に割り当てています。この区別にはストップウォッチが付いてきます。502 はたいていミリ秒で返り、504 は必ず何かのタイマーがちょうど切れたところで返ります。最後に、502 は RFC 9110 §15.1 のヒューリスティックにキャッシュ可能なコードの一覧に入っていないので、中間装置が独断で保存することはありません。nginx は既定で `error` と `timeout` のときに次の upstream へ回しますが、`proxy_next_upstream` に `non_idempotent` を明示しない限り、非冪等なリクエストは回しません。
| 紛らわしいコード | 見分け方 |
|---|---|
| 504 | どちらもプロキシが upstream について報告しているもので、線引きは RFC 9110 が済ませています。502 は不正な応答、504 は時間内に来なかった応答です。文言よりも時計を読んでください。502 はたいてい 1 秒どころではない速さで返ります。upstream が接続を拒否したか、途中で切ったからです。504 はきりのいい数字で返ります——nginx の既定の `proxy_read_timeout` なら 60 秒、Cloudflare なら 125 秒。何かが座ってタイマーを待ち切ったからです。 |
| 500 | 500 はアプリケーションが自分の失敗を認めたもの、502 はアプリケーションが使える答えを渡してこなかったとプロキシが報告したものです。フレームワークのエラーハンドラーが走ったならステータスは 500 で、そのログに例外が残り、プロキシはその応答を素通しします。502 はハンドラーが走る前にワーカーが死んだ場合が多く、アプリケーションのログは空っぽで、何かが起きた記録はプロキシのログにしか残りません。 |
| 503 | 503 は「一時的に処理できない」で、たいていは意図的です——メンテナンスモード、ヘルスチェックがバックエンドをローテーションから外した、レートリミッターが効いた。RFC 9110 は 503 が `Retry-After` を伴うことを認めていますが、502 にそういう慣習はありません。つまり 503 は誰かがあなたを断ると決めたということ、502 はそもそも答えが返せなかったということです。 |
| 521 | 521 は IETF ではなく Cloudflare 自身のコードで、まさにこのケースが一般的な 502 として報告されないために存在します。Cloudflare からオリジンへの接続がはっきり拒否された、という意味です。502 ではなく 521 が出るということは、問題がオリジンの待ち受けかファイアウォールに一手で絞り込めるということで、それが 520 番台がそもそも作られた理由です。 |
nginx | nginx はクライアントに 502 を書く前に、自分のエラーログに理由を書きます。その 1 行は 3 桁の数字よりはるかに具体的です。`connect() failed (111: Connection refused)`、`upstream prematurely closed connection`、`upstream sent too big header`、`no live upstreams` は 4 つの別々の問題ですが、ブラウザにはまったく同じ顔で届きます。 |
どれもブラウザ内で完結します。アップロードは発生しません。
502 を調べているとき、ついでに読むことになりがちなコードです。
ページではなく応答ヘッダーを読んでください。curl -sS -o /dev/null -D - https://example.com/path が本文なしでヘッダーだけを出し、server・via・cf-ray の 3 つで、答えたのがどの層かが分かります。cf-ray の値があれば応答を扱ったのは Cloudflare で、その値はサポートが聞いてくる ID です。エッジを完全に外して確かめたいなら、--resolve example.com:443:203.0.113.10 を付けて同じリクエストを投げ直します。元のホスト名と SNI を送りつつ、指定したオリジンのアドレスへ接続するので、答えが変われば、エッジとオリジンの言い分が食い違っているということです。
ありません。分岐条件にしてはいけません。RFC 9112 はクライアントに理由句を無視するよう求めていますし、サーバーは自由に変えられますし、HTTP/2 と HTTP/3 にはそもそも理由句が存在しません。HTTP/1.1 では 404 Not Found として届くものが、HTTP/2 では素の 404 として届きます。このサイトが登録済みの理由句を載せているのは、それが検索される語であり HTTP/1.1 のログに出る文字列だからで、何かのソフトウェアがそれに依存しているからではありません。
5xx と 429 は再試行してよく、4xx はしないでください。次に投げても中身は同じだからです。応答に Retry-After が付いていればそれに従ってください。RFC 9110 がまさにこのために定義していて、値は秒数でも日時でも構いません。付いていなければ、ジッター付きの指数バックオフと明確な上限を使い、しかも冪等なメソッドに限ってください。再試行された POST はカードに二重で課金しかねません。すでに失敗しているサーバーへの再試行の嵐は、短い障害を長い障害に変える典型的な経路です。
変えられます。守るべき規則は 1 つだけ、そのコードが本当であること。応答を自動で読むもの——検索エンジン、監視、キャッシュ、クライアント側の再試行ロジック——はすべて数字だけで判断し、ページの中身は一切見ません。エラーページを 200 で返せば自分の監視から障害が隠れますし、無いページを 200 で返せばエラーが本文として索引されますし、単に不正なリクエストに 500 を返せば、当番の人をスタックの間違った半分へ送り込むことになります。
502 でまだ詰まっているなら、ステータスコード一覧をすべて見る。あるいは上に戻って、自分の立ち位置に向けて書かれたブロックを読んでください。