ByteScope

503

HTTP 503 Service Unavailable の直し方

5xx サーバーエラーIETF 標準RFC 9110

何かが意図的に、そして「今は」応じないことを決めました。メンテナンスページ、レート制限、あるいは健全な転送先を一つも持たないロードバランサー——だから 503 は、5xx で唯一「期限」が付いてくるコードです。

503 が出ている理由と、やるべきこと

同じ 3 桁でも、リクエストのどちら側にいるかで別々の問題になります。自分に当てはまるブロックを読んでください。

curl で 503 を再現する

失敗した URL に対してそのまま実行してください。エラーページを表示せずにステータスだけを出すので、ブラウザが描画したものではなくサーバーが言ったことが見えます。

curl
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 はオリジン発です。

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 が返るのは、リクエストを受け取れる状態のターゲットが足りないという意味だと説明しています。これはアプリケーションの障害ではなくヘルスチェックの結果です。

503 と間違えられやすいコード

紛らわしいコード見分け方
429429 (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 は「何かが尋ねるのをやめた」と読んでください。
500500 は予期しない状況、503 は予期された状況です。RFC 9110 は 503 に `Retry-After` という伝達路を与え、500 には何も与えていません。未処理の例外がいつ止むのかについて、言えることが何も無いからです。例外を投げることで負荷を捨てているサービスは、計画された判断をクラッシュとして報告していることになり、503 を基準にバックオフするクライアントはすべて即座に再試行してきます。
nginxnginx における 503 が、アプリケーションのものであることはほとんどありません。`limit_req` も `limit_conn` も既定でこれですし、メンテナンスのブロックはたいていそのまま `return 503` ですし、エラーログにはレート制限の拒否が `limit_req_log_level` で設定した水準で記録され、遅延は拒否より 1 段低い水準で記録されます。そのログを制限ゾーンの名前で grep すれば、見ているのが制限装置なのか、同じ 3 桁を着た別の何かなのかが 1 行で分かります。
Retry-AfterRFC 9110 §10.2.3 はこのフィールドを 2 つの形——非負の秒数、または HTTP-date——で定義しているので、片方しかパースしないクライアントは世界のサーバーの半分を黙って取り違えます。503 と一緒に送られたときは、サービスがどれくらい利用できない見込みかを表します。3xx と一緒のときは意味が違い、リダイレクト先へリクエストを出すまでの最短時間になります。両方の形をパースし、ヘッダーが無い場合は「不明」として扱ってください。「今すぐ再試行」ではありません。
理由句
Service Unavailable
クラス
5xx サーバーエラー
定義
RFC 9110
標準
IETF 標準

このサイトのツール

どれもブラウザ内で完結します。アップロードは発生しません。

関連するステータスコード

503 を調べているとき、ついでに読むことになりがちなコードです。

よくある質問

このステータスコードを出したのがどのサーバーかを知るには?

ページではなく応答ヘッダーを読んでください。curl -sS -o /dev/null -D - https://example.com/path が本文なしでヘッダーだけを出し、serverviacf-ray の 3 つで、答えたのがどの層かが分かります。cf-ray の値があれば応答を扱ったのは Cloudflare で、その値はサポートが聞いてくる ID です。エッジを完全に外して確かめたいなら、--resolve example.com:443:203.0.113.10 を付けて同じリクエストを投げ直します。元のホスト名と SNI を送りつつ、指定したオリジンのアドレスへ接続するので、答えが変われば、エッジとオリジンの言い分が食い違っているということです。

数字のあとの理由句(Not Found など)に意味はありますか?

ありません。分岐条件にしてはいけません。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 でまだ詰まっているなら、ステータスコード一覧をすべて見る。あるいは上に戻って、自分の立ち位置に向けて書かれたブロックを読んでください。