【実務・中級編】HTTP/1.1ステータスコード5xx(Server Error)の発生原因とインフラ側の対応 – HTTPプロトコル・通信規格実践ガイド

5xxエラーは「インフラからのSOS」だ。そのパケットの悲鳴を読み解く術を伝授しよう

現場でエンジニアをやっていると、深夜2時にPagerDutyが鳴り響く。Slackには「502が急増しています」という殺伐としたアラートが流れる。君たちが遭遇するその5xx系のステータスコードは、単なるWebサーバーの不機嫌さではない。それは、複雑に絡み合ったネットワークとアプリケーションの連携が、どこかで断絶したという「インフラからの切実なSOS」なんだ。

今日は、HTTP/1.1の標準仕様をベースに、現場で叩き上げられた「5xx系トラブルシューティングの解剖学」を共有しよう。

—

1. 500 Internal Server Error:深淵なるブラックボックス

500エラーは最も厄介だ。クライアント(ロードバランサーやブラウザ)から見れば、「サーバーが何かでコケた」としか分からない。

発生要因:

  • アプリケーションの未捕捉例外(NullPointerExceptionなど)
  • データベース接続プールの枯渇
  • 権限設定ミスによるファイル読み込み失敗

実務的対策:
まずはログを見ろ。だが、ログが出力されていないなら、それは「プロセスが起動すらしていない」か「スタックトレースが標準出力から漏れている」可能性が高い。

Python/Flaskでのデバッグ用ハンドリング例
@app.errorhandler(500)
def handle_500(e):
# ログには詳細なトレースバックを吐き出し、ユーザーには抽象的なメッセージを返すのが鉄則
app.logger.error(f”Server Error: {e}”)
return {“error”: “Internal Server Error”}, 500

—

2. 502 Bad Gateway:仲介役の絶望

ロードバランサー(NginxやAWS ALB)がバックエンドサーバー(Gunicorn, Node.js等)に接続を試みたが、不正なレスポンスが返ってきた、あるいは接続が拒否された状態だ。

発生要因:

  • バックエンドプロセスがダウンしている(死活監視の盲点)
  • 上流からのリクエストを処理しきれず、TCPバックログが溢れている
  • ソケット通信のタイムアウト(Upstreamが応答を返さずTCP RSTが返る)

Nginxの設定チェックポイント:

upstream backend_app {
server 127.0.0.1:8000;
keepalive 32; # これを適切に設定しないとTCPコネクションの乱立で502を誘発する
}

location / {
proxy_pass http://backend_app;
# バックエンドが死んだ時、速やかに切り替えるためのタイムアウト設定
proxy_connect_timeout 5s;
proxy_read_timeout 10s;
}

—

3. 503 Service Unavailable:過負荷という名の自衛

「今は忙しいから無理」という、サーバーからの丁重な(あるいは必死な)お断りだ。

発生要因:

  • メンテナンスモード
  • リクエスト過多によるスレッドプール枯渇
  • バックエンドのロードアベレージ高騰による意図的な拒否

デバッグのヒント:
もし君が `curl` で確認するなら、以下のフラグを付けてヘッダーを見てほしい。
`curl -Iv https://your-api.com/`
ここで `Retry-After` ヘッダーが返ってきていないか確認すること。これがあるなら、サーバーは「あと〇〇秒待ってくれ」と言っているんだ。

—

4. 504 Gateway Timeout:終わらない待ち時間

ロードバランサーはバックエンドへリクエストを投げた。しかし、バックエンドが処理を終える前に、ロードバランサー側のタイムアウト制限が先に発動した。

なぜこれが危険か:
504が発生しても、バックエンド側の処理は裏で動いている可能性が高い。二重決済やデータの二重登録を引き起こすトリガーになるからだ。

設計上の解決策:
長時間かかる処理は、HTTPリクエスト内で完結させてはいけない。非同期キュー(RabbitMQやRedis+Celery)を用いたタスク駆動型に変更するのが、シニアエンジニアの流儀だ。

—

トラブルシューティングの鉄則:パケットの視点を持て

最後に、現場で必ずやってほしい「確認フロー」を伝授する。

1. レイヤーを確認せよ: ロードバランサーのログと、バックエンドサーバー(コンテナ)のログを突き合わせろ。「ロードバランサー側で502だが、バックエンドにはアクセスが来ていない」なら、それはネットワーク(Security GroupやFirewall)の遮断だ。
2. コネクションのライフサイクルを見よ:

# 現在の接続状態を確認する
netstat -an | grep 8000 | grep ESTABLISHED | wc -l

もしバックエンドのコネクション数が振り切れているなら、それはアプリの処理速度がボトルネックだ。
3. 再現コードで切り分けろ:
Fetch APIを使って、特定のヘッダーを付与したリクエストを投げ、どのタイミングでエラーになるかを確認する。

// Fetch APIでの簡易検証コード
async function checkStatus() {
const res = await fetch(‘https://api.example.com/data’, {
method: ‘GET’,
headers: { ‘X-Debug-Mode’: ‘true’ } // インフラ側でログを拾いやすくするためのカスタムヘッダー
});
console.log(`Status: ${res.status}`);
}

5xxエラーは、システムが限界に達したという「進化のサイン」でもある。怖がることはない。ログという名のパケットの足跡を追い、ボトルネックを特定し、構造を最適化する。それこそが、我々インフラエンジニアの醍醐味なんだ。

さあ、モニターの向こう側の「真実」を見つけに行こう。何か行き詰まったら、いつでもまた聞きに来るといい。

コメント

タイトルとURLをコピーしました