ByteScope

500

HTTP 500 Internal Server Error の直し方

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

アプリケーション自身が動いて、想定していなかった何かに当たって諦めた——という意味です。だから 502 や 504 と違って、どこかにスタックトレースが必ずあります。仕事はそれがどのログにいるかを見つけることです。

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

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

curl で 500 を再現する

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

curl
curl -sS -o /dev/null -D - -w '\n%{http_code} in %{time_total}s\n' -H 'content-type: application/json' --data '{"quantity":-1}' https://example.com/api/orders

`-sS` は進捗表示だけを消してエラーメッセージは残し、`-o /dev/null` はボディを捨て、`-D -` は応答ヘッダーを標準出力に落とし、`--data` はペイロードを送ってリクエストを POST にし、`-w` はステータスと実時間を出します。まずヘッダーのブロックを読んでください。ステータス行には答えられない唯一の問い——これを書いたのはどの層か——に答えてくれます。`server:` にアプリケーションのフレームワーク名が出ている、あるいは自分のスタックが付けている `x-request-id` があるなら、リクエストはコードまで届いていて、例外はその ID でログの中にいます。`server: nginx` や `server: cloudflare` だけでアプリケーションのヘッダーが一つも無いなら、見ているのはアプリケーションの手前から返ったエラーページで、3 桁の数字を読み直す価値があります。使える答えを取れなかった中間装置が報告するのは 500 ではなく 502 だからです。2 つ目の手がかりは経過時間です。アプリケーション階層の 500 はたいてい成功と同じ速さで返ります。例外は待った末ではなく投げられたものだからです。だから 30 秒かかる 500 はコードの中のタイムアウトで、投げた行ではなく固まった依存先を指しています。正常だと分かっているペイロードに差し替えてエンドポイント自体が動くことを確かめ、失敗するほうは取っておいてください。それが再現手順であり、バックエンドがログからは復元できないものです。

500 が実際に意味していること

RFC 9110 §15.6.1 は 500 を、サーバーが予期しない状況に遭遇してリクエストを果たせなかった状態と定義しています。その外側の 5xx クラスを §15.6 は「サーバーがリクエストを実行できない」と紹介しています。この定義は仕様の中でわざといちばん具体性が低く、そこが役に立つところです。500 は診断ではなく自白なので、コード自体は何が壊れたかを一切運びません。運ぶのは「誰が気づいたか」だけです。ここから 3 つの帰結が出ます。1 つ目、アプリケーションが出した 500 は完全に正当な HTTP 応答なので、手前のプロキシは自前の応答に差し替えず、そのまま中継します。RFC 9110 §15.6.3 が、中間装置が不正な応答を受け取った場合のために 502 を取ってあるのはこのためですし、500 は「コードが走った証拠」で、502 はしばしば「走ってすらいない」という意味になるのもこのためです。2 つ目、500 は RFC 9110 §15.1 のヒューリスティックにキャッシュ可能なステータスコードの一覧——200・203・204・206・300・301・308・404・405・410・414・501、それだけです——に入っていません。だから中間装置が独断で保存することはなく、居座り続ける 500 はキャッシュから配られているのではなく、リクエストのたびに作り直されています。3 つ目、500 はそのリクエストに効果があったかどうかについて何も言いません。予期しない状況に当たるまでにサーバーがどこまで進んだのかを知る手段を、仕様はクライアントに与えていません。決済や注文の 500 が自動的に再試行して安全とは言えない理由がこれです。サーバーはアプリケーションのコードだけでなく、自分自身の機構からも 500 を出します。Apache の CGI のドキュメントは、エラーログに「Premature end of script headers」と出る「Internal Server Error」を、スクリプトが HTTP ヘッダーより先に何かを出力したか、suexec の権限チェックが拒否したかだと説明していますし、nginx は rewrite や内部リダイレクトの循環といった自分の内部障害に 500 を返します。どちらの場合も 3 桁の数字は同じで、内容のすべてはログの 1 行にあります。

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

紛らわしいコード見分け方
502500 はアプリケーションが自分の失敗を認めたもの、502 は中間装置が「アプリケーションから使える答えを受け取れなかった」と報告したものです。RFC 9110 §15.6.3 は 502 を、内側のサーバーから受け取った不正な応答と定義していて、アプリケーション自身の 500 は正当な応答なので、プロキシはそのまま素通しします。実務的には、500 なら探すべきスタックトレースが存在し、502 はしばしばハンドラが走る前にワーカーが死んでいて、アプリケーションのログが空です。
503503 は判断で、500 は事故です。RFC 9110 §15.6.4 は 503 を、過負荷や計画されたメンテナンスのためにサーバーが一時的にリクエストを扱えない状態と定義し、どれだけ待てばいいかを示す `Retry-After` を載せられるようにしています。500 のほうには「待て」を示唆するものが何もありません。通る見込みが何も無いからです。負荷を捨てるのに例外を投げているサービスは、計画された拒否をクラッシュとして報告していることになり、503 をキーにしたクライアントのバックオフはすべて空振りします。
400RFC 9110 §15.5.1 は 400 を、クライアント側の誤りと受け取れる何かのせいでサーバーがリクエストを処理できない状態と定義しています。エンドポイントが拒む入力に対しては、これが正直なコードです。入力の検証中に上がる 500 は、サーバーではなくエラー処理のバグです。答えられるリクエストで、答えは「駄目」だったのに、5xx はクライアントに「絶対に成功しないもの」の再試行を促してしまいます。
Cloudflare 520Cloudflare 越しならこの 2 つは簡単に見分けられますし、知っておく価値があります。Cloudflare は 520 を、オリジンが空・不明・予期しない応答を返した場合——128 KB の上限を超えるヘッダーも含みます——と説明しています。つまり 520 は、オリジンの答えが使える HTTP ではなかったという意味です。Cloudflare 越しに届く 500 はその逆で、アプリケーションが意図的に生成した整った応答を、Cloudflare がそのまま中継したものです。
501RFC 9110 §15.6.2 は 501 を、リクエストを果たすのに必要な機能をサーバーがサポートしていない状態と定義し、メソッドを認識できない場合の適切な答えとして名指ししています。500 と違って、1 件のリクエストがうまくいかなかったという話ではなくサーバーの能力についての表明ですし、RFC 9110 §15.1 がヒューリスティックにキャッシュ可能と挙げる数少ないエラーコードの一つでもあります。だから誤った 501 はキャッシュに保存され、原因が消えたあとも繰り返されえます。500 にはそれが起きません。
理由句
Internal Server Error
クラス
5xx サーバーエラー
定義
RFC 9110
標準
IETF 標準

このサイトのツール

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

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

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

よくある質問

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

ページではなく応答ヘッダーを読んでください。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 を返せば、当番の人をスタックの間違った半分へ送り込むことになります。

500 でまだ詰まっているなら、ステータスコード一覧をすべて見る。あるいは上に戻って、自分の立ち位置に向けて書かれたブロックを読んでください。