503
何かが意図的に、そして「今は」応じないことを決めました。メンテナンスページ、レート制限、あるいは健全な転送先を一つも持たないロードバランサー——だから 503 は、5xx で唯一「期限」が付いてくるコードです。
同じ 3 桁でも、リクエストのどちら側にいるかで別々の問題になります。自分に当てはまるブロックを読んでください。
これが出ている理由
一斉に、そしてどこでも起きます。すべてのエンドポイントで、たいてい HTML の文書自体でも。そしてアプリケーションらしさのまるで無いエラーページが出ます。それを書いたのはアプリケーションではないからです。本物の障害と分けてくれる目印は `Retry-After` ヘッダーで、これがあれば拒否は意図的で時間が区切られています。罠はすでに書いてある再試行ループです。5xx を「すぐもう一度」と扱うクライアントは、レート制限を自作自演のサービス妨害に変えますし、メンテナンスの終了と同時に押し寄せる群れを作ります。
やること
何かを決める前に `Retry-After` を読み、それに従ってください。秒数か HTTP-date のどちらかで、両方とも RFC 9110 §10.2.3 の定義なので、両方の形をパースします。無い場合は、ジッター付きの指数バックオフと試行回数の明確な上限で下がってください。すべてのクライアントが同じきりのいい数字で目を覚ますことが、回復中のサービスの回復を妨げます。利用者には汎用の失敗ではなく具体的な状態——利用できません、まもなく再試行します——を見せてください。503 は終わることが前提ですし、汎用のエラー表示はページのリロードを誘って、さらに 1 リクエストを消費させます。書き込みはここでも慎重に。503 はたいてい処理される前に拒否されたという意味ですが、ステータスだけではそれを証明できないので、課金や作成を伴うものはコードに頼らず冪等キーを持たせてください。
これが出ている理由
3 つの別々の層が 503 を生み、外からは区別がつきません。意図的なものかもしれません——メンテナンス用の `return 503` や、負荷を捨てるフィーチャーフラグ。制限装置かもしれず、nginx ではそれが既定です。`limit_req_status` と `limit_conn_status` は 503 として文書化されているので、あなたのレート制限はレート制限ではなく障害として報告されています。あるいはアプリケーションの上での健全性の判断かもしれません。健全なターゲットが一つも無い Application Load Balancer——AWS が「リクエストを受け取れる状態のターゲットが不足」と説明する状態——か、`retry` の窓(既定 60 秒)のあいだワーカーをエラー状態に留める Apache の `mod_proxy` です。
やること
何かに触る前に、どの層が答えたのかを突き止めてください。アクセスログが「記録が無いこと」でそれを教えてくれます。そのリクエストを記録した層は拒否の位置かそれより上、記録が一切無い最初の層はその下です。アプリケーションのログに一度も届かない 503 を書いたのはプロキシか制限装置かバランサーで、アプリケーションのコードを読む時間は無駄になります。層が分かれば、問うべきことは層ごとに違います。制限装置なら、そのレートが妥当か、`limit_req_status 429` のほうが正直な答えではないか。バランサーなら、ヘルスチェックが実際に何を叩いているか、そして本番のトラフィックとは共有しない理由で落ちていないか——データベースに触るヘルスエンドポイントは、アプリの他の部分なら乗り切れた障害でも落ちます。自分が選んだ拒否には必ず `Retry-After` を付けてください。行儀のよいクライアントが叩き続けずに待てる唯一の手がかりです。そして回復の窓を確認すること。Apache の既定の `retry` が 60 秒なら、直ったバックエンドは健全になってから最大 1 分間 503 を返し続けます。「修正が効かなかった」と思い込むには十分な長さです。
これが出ている理由
サイトは動いていますが、今は意図的に応じていません。壊れているのとは違います。誰かがメンテナンスをしているか、サイトが捌ける以上の負荷を受けていて、自分を守るために人を追い返しているかです。ページにはだいたいの所要時間が書かれていることが多く、たいていは何時間ではなく何分の話です。全員に影響するので、あなたのアカウントや端末や回線の話ではありません。
やること
ページが示す時間だけ待って、それから 1 回だけリロードしてください。原因が過負荷のときに更新を繰り返すのは逆効果です。リロードのたびに、サイトがすでに捌けていないリクエストが 1 つ増えます。Cookie を消したりアプリを入れ直したりルーターをリセットしたりしても、原因には何も触れません。お金のかかる手続きの途中だったなら、やり直す前に完了しているかどうかを確認してください。時間の目安が無く、数分を超えて続くなら、答えがいちばん早く出るのはサイトのステータスページか公式アカウントです。運営者はもう気づいています。
失敗した URL に対してそのまま実行してください。エラーページを表示せずにステータスだけを出すので、ブラウザが描画したものではなくサーバーが言ったことが見えます。
curl -sS -o /dev/null -D - -w '\n%{http_code} retry-after=%header{retry-after} in %{time_total}s\n' https://example.com/api/`-D -` が応答ヘッダーを出し、`%header{retry-after}` がそのフィールドだけを要約行に引っ張り出します。これは最近の curl で文書化されている `--write-out` の変数で、それが無いほど古いビルドでも `-D -` のダンプにヘッダーは出ます。出力から分かることは 3 つに分かれます。値の入った `retry-after` は、拒否が意図的で誰かが期間を選んだという意味です。計画メンテナンスや制限装置と、単に健全な転送先を持たないロードバランサーを分けるいちばん速い方法です。空なら、拒否した何かは RFC 9110 §15.6.4 の示唆を無視していて、あなたが決める待ち時間はすべて推測になります。`server:` ヘッダーはそのページを書いた層を名指しし、設定を読むべきなのはその層です。経過時間が拒否と失敗を分けます。制限装置やメンテナンスのルールはミリ秒で答えます。上流に一切問い合わせていないからです。制限装置が原因かどうかを確かめるには、設定されたレートより速くリクエストを繰り返します——`for i in $(seq 1 50); do curl -sS -o /dev/null -w '%{http_code} ' https://example.com/api/; done`——そして並んだステータスが途中で 200 から 503 に切り替わるのを見ます。これは制限装置の署名であって、他の何ものでもありません。`--resolve example.com:443:203.0.113.10` を足すと、元のホスト名と SNI を送りつつオリジンのアドレスへ直接つなぐので、消える 503 はエッジ発、残る 503 はオリジン発です。
RFC 9110 §15.6.4 は 503 を、一時的な過負荷または計画されたメンテナンスのためにサーバーが現在リクエストを扱えず、しばらくすれば解消される見込みがある状態と定義し、どれだけ待つべきかを示す `Retry-After` ヘッダーフィールドをサーバーが送ってもよい (MAY) としています。このヘッダーこそが、503 を他のすべての 5xx と構造的に違うものにしています。§10.2.3 は `Retry-After` を秒数または HTTP-date と定義していて、503 と一緒に送られたときは、そのサービスがどれくらい利用できない見込みかを表明します。ここで MAY という語が効いています。`Retry-After` の無い 503 は完全に適合しているので、ヘッダーが無いことから分かるのは「拒否した相手が、いつ戻ってくればいいかを言わなかった」ということだけです。それに、そもそも接続を受け付ける余力すら無くなったサーバーは 503 ではなく接続エラーを返すので、ダッシュボードに 503 が出ていないことは、何も過負荷でなかった証拠にはなりません。見ている間に効いてくる性質があと 2 つ。503 は RFC 9110 §15.1 が挙げるヒューリスティックにキャッシュ可能なコード——200・203・204・206・300・301・308・404・405・410・414・501——に入っていないので、指示しない限り何も保存しませんし、原因より長生きする 503 は再生ではなく毎回作り直されています。そして 503 は、あなたのリクエストについては何も言いません。サービスについての表明であり、まさにその線が 429 との境目です。実際の出どころがアプリケーションのコードであることはめったにありません。nginx のレート制限は既定でこれです。`limit_req_status` も `limit_conn_status` も既定値 503 として文書化されているので、設定したレートを超えて弾かれたリクエストは、ディレクティブを変えない限り 503 として届きます。Apache の `mod_proxy` は、失敗したバックエンドがコネクションプールのワーカーをエラー状態に入れ、以後 httpd は「タイムアウトが切れるまでそのサーバーへリクエストを転送しない」と説明しています——`retry` パラメータ、既定 60 秒です。だからそこでの拒否は、それを直した再起動より長生きしえます。そして AWS は、Application Load Balancer から一貫して HTTP 503 が返るのは、リクエストを受け取れる状態のターゲットが足りないという意味だと説明しています。これはアプリケーションの障害ではなくヘルスチェックの結果です。
| 紛らわしいコード | 見分け方 |
|---|---|
| 429 | 429 (RFC 6585 §4) は「他ならぬあなたが送りすぎ」、503 は「サービスが全員に対して利用できない」です。この 2 つがぼやける理由は既定値です。nginx は `limit_req_status` と `limit_conn_status` を 503 として文書化しているので、インターネット上のレート制限の大半は障害として報告されています。制限装置を運用しているなら 429 に設定してください。クライアントはサービス全体が落ちたと決めつけず、その 1 つの呼び出しだけを控えられますし、ダッシュボードが絞りをダウンタイムとして数えるのも止まります。 |
| 502 | 同じ出来事が、どのプロキシを使っているかによって別のコードになりえます。番号だけでは診断にならない理由です。nginx は、正当な応答を得られないバックエンドを 502 として報告します。一方 Apache の `mod_proxy` は失敗したワーカーを輪番から外し、`retry` の窓(既定 60 秒)のあいだ転送そのものを拒みます。だからクライアントが見るのはその拒否です。502 は「何かが答えられなかった」、503 は「何かが尋ねるのをやめた」と読んでください。 |
| 500 | 500 は予期しない状況、503 は予期された状況です。RFC 9110 は 503 に `Retry-After` という伝達路を与え、500 には何も与えていません。未処理の例外がいつ止むのかについて、言えることが何も無いからです。例外を投げることで負荷を捨てているサービスは、計画された判断をクラッシュとして報告していることになり、503 を基準にバックオフするクライアントはすべて即座に再試行してきます。 |
nginx | nginx における 503 が、アプリケーションのものであることはほとんどありません。`limit_req` も `limit_conn` も既定でこれですし、メンテナンスのブロックはたいていそのまま `return 503` ですし、エラーログにはレート制限の拒否が `limit_req_log_level` で設定した水準で記録され、遅延は拒否より 1 段低い水準で記録されます。そのログを制限ゾーンの名前で grep すれば、見ているのが制限装置なのか、同じ 3 桁を着た別の何かなのかが 1 行で分かります。 |
Retry-After | RFC 9110 §10.2.3 はこのフィールドを 2 つの形——非負の秒数、または HTTP-date——で定義しているので、片方しかパースしないクライアントは世界のサーバーの半分を黙って取り違えます。503 と一緒に送られたときは、サービスがどれくらい利用できない見込みかを表します。3xx と一緒のときは意味が違い、リダイレクト先へリクエストを出すまでの最短時間になります。両方の形をパースし、ヘッダーが無い場合は「不明」として扱ってください。「今すぐ再試行」ではありません。 |
どれもブラウザ内で完結します。アップロードは発生しません。
503 を調べているとき、ついでに読むことになりがちなコードです。
ページではなく応答ヘッダーを読んでください。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 を返せば、当番の人をスタックの間違った半分へ送り込むことになります。
503 でまだ詰まっているなら、ステータスコード一覧をすべて見る。あるいは上に戻って、自分の立ち位置に向けて書かれたブロックを読んでください。