冒頭まとめ

アクセスログに残る 499 は、Nginx が応答を返す前にクライアント側から接続が切られたことを示すコードです。HTTP の標準にはない Nginx 独自のコードで、相手はすでにいないため、利用者のブラウザにこのコードが表示されることはありません。つまり 499 は「エラー画面が出る問題」ではなく「ログにだけ現れる兆候」です。

原因は、クライアントが先に諦めた理由が何か、という問いに置き換えられます。典型は3つで、上流の応答が遅くクライアント側の制限時間が先に切れた、利用者が操作を中断した(画面を閉じた・再読み込みした)、Nginx の手前にいる中継役(ロードバランサーなど)や監視の仕組みが応答を待たずに切断した、のいずれかです。切り分けの鍵は処理時間の記録ですが、既定のログ形式には含まれないため、まず $request_timeログに加えるところから始めます。

エラーの概要

Nginx のソースコードには、499 を定義した箇所に説明が書かれています。要約すると、リクエストの処理中にクライアントが接続を閉じた場合を表すコードHTTP には定義されていないため、応答ヘッダーを送ろうとする前にクライアントが接続を閉じていた状況を記録する目的で、独自のコードとして導入した、という内容です。

504 Gateway Timeout との対比で理解すると分かりやすいコードです。上流の応答が遅いとき、Nginx 側が待ちきれずに打ち切ればクライアントに 504 が返ります。Nginx がまだ待っている間にクライアント側が先に切れば、誰にも何も返らず、ログに 499 が残ります。同じ「遅い」という状況でも、どちらが先に諦めたかで記録が変わります。

診断上の重要な特徴が2つあります。第一に、499 はエラーログには info レベルでしか記録されません。エラーログの既定のレベルでは何も出力されないため、「アクセスログに 499 があるのにエラーログに手がかりがない」のは正常な動作です。第二に、既定では、クライアントの切断を検知した時点で Nginx は上流への接続も閉じます(この動作は後述の proxy_ignore_client_abort で変えられます)。ただし上流のアプリケーションが切断を検知しない作りの場合、応答の届け先がないまま処理だけが完走することもあります。

アクセスログには次のように記録されます。応答を送っていないため、送信バイト数が 0 になるのが典型です。

192.168.1.100 - - [09/Jul/2026:14:22:10 +0900] "POST /api/report HTTP/1.1" 499 0 "-" "python-requests/2.31.0"

まず最初に:処理時間をログに出す

既定の combined 形式には、リクエストの処理にかかった時間が含まれません。499 の切り分けには時間の情報が不可欠なので、log_format$request_timeリクエスト全体の処理時間)と $upstream_response_time(上流の応答にかかった時間)を加えます。どちらも公式ドキュメントに記載のある変数です。

http {
    log_format timed '$remote_addr - $remote_user [$time_local] "$request" '
                     '$status $body_bytes_sent "$http_referer" "$http_user_agent" '
                     'rt=$request_time urt=$upstream_response_time';

    access_log /var/log/nginx/access.log timed;
}

反映後に記録される 499 の行を見て、次のように読み分けます。

rt の値が大きく、しかも複数の 499 でほぼ同じ秒数(例:どれも約30秒)に揃っている場合、その秒数はクライアント側の制限時間です。上流の応答がそれより遅いために、クライアントが毎回同じところで諦めています(原因1)。

rt の値がばらばらで、発生も散発的な場合は、利用者の操作による中断が中心です(原因2)。

rt が小さいのに 499 が繰り返される、または特定の送信元に集中している場合は、手前の中継役や監視の仕組みによる切断を疑います(原因3)。

よくある原因と解決手順

原因1:上流の応答が遅く、クライアント側の制限時間が先に切れる

最も多い原因です。ブラウザは比較的長く待ちますが、プログラムから呼び出す HTTP クライアント(スマホアプリや他システムからの API 呼び出しなど)には、それぞれ独自の制限時間があり、短めに設定されていることが多いためです。上流の処理が遅ければ、Nginx が 504 を返すより先にクライアントが切断し、499 が積み上がります。

対処の本筋は、上限に達している処理を速くすることです。urt(上流の応答時間)が大きいことを確認したうえで、該当の処理を上流アプリケーション側で改善します。ログを集計すると、どの URL に 499 が集中しているかが分かります。

# 499 が多い URL の上位を集計
sudo grep " 499 " /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head

上流の改善に時間がかかる間の暫定策として、クライアント側の制限時間を処理の実態に合わせて延ばす方法もあります。ただしどのクライアントにも制限時間を延ばせるとは限らないため、恒久策は上流の改善です。

原因2:利用者の操作による中断

利用者がページの読み込みを止めた、再読み込みした、別のページへ移動した、という場合も、接続は途中で切られるため 499 が記録されます。これは正常な利用の範囲であり、散発的な 499 をゼロにすることはできませんし、する必要もありません。

見るべきは率と偏りです。全体の件数に対して 499 の割合が高い、あるいは特定のページに集中している場合、それは利用者が待ちきれずに操作している、つまりそのページが遅いことのサインです。この場合、実態は原因1と同じで、対処も同じ(遅い処理の改善)になります。

原因3:手前の中継役や監視の仕組みが切断している

Nginx の手前にロードバランサーCDN(配信の中継サービス)がいる構成では、アクセスログの $remote_addr は利用者ではなく中継役のアドレスになります。499 が特定の内部アドレスからばかり記録されている場合、切断しているのは利用者ではありません。

典型例は2つです。1つは、中継役側の制限時間が Nginx の待ち時間(proxy_read_timeout など)より短く、Nginx が上流を待っている間に中継役が先に切断するケースです。この場合、上流は健全でも 499 が出続けます。対処は制限時間の整合で、手前の制限時間は Nginx の待ち時間より長くします。もう1つは、死活監視(ヘルスチェック)が接続だけ確認して応答を待たずに切る作りになっているケースで、これは監視のたびに 499 が記録されます。監視元のアドレスと発生間隔の規則性で見分けられます。

補足:proxy_ignore_client_abort

公式ドキュメントのとおり、proxy_ignore_client_abort は、クライアントが応答を待たずに接続を閉じたときに、上流(プロキシ先)への接続も閉じるかどうかを決める設定です。既定は off で、クライアントの切断と同時に上流への接続も閉じられ、499 が記録されます。on にすると、クライアントが切断しても上流への処理は続行されます。

location /api/long-task {
    proxy_pass http://127.0.0.1:8080;
    proxy_ignore_client_abort on;
}

途中で切断されると困る処理(打ち切りに対応していない上流など)に限定して使う設定であり、499 のログを消す目的で全体に on にする設定ではありません。on にした分だけ、誰も受け取らない処理のためにサーバーの資源が使われることになります。

切り分けの順序

  1. log_format$request_time$upstream_response_time を加え、時間付きで 499 を記録できるようにする。
  2. 499 の行の rt を読む。特定の秒数に揃っていればクライアント側の制限時間であり、上流の遅さが原因(原因1)。ばらばらで散発的なら利用者の中断(原因2)。
  3. $remote_addr を確認する。中継役や監視元の内部アドレスに集中していれば、切断しているのは利用者ではない(原因3)。制限時間の整合と監視の作りを確認する。
  4. URL 別に集計し、499 が集中している処理を特定して上流側で改善する。

確認コマンド集

# 1. 499 の発生状況を確認
sudo grep " 499 " /var/log/nginx/access.log | tail -20

# 2. 499 が多い URL の上位を集計
sudo grep " 499 " /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head

# 3. 499 の送信元アドレスの偏りを確認(中継役・監視の切り分け)
sudo grep " 499 " /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head

# 4. 有効な設定からログ形式と待ち時間の設定を確認
sudo nginx -T | grep -nE "log_format|access_log|proxy_read_timeout|proxy_ignore_client_abort"

# 5. 設定の文法確認とリロード
sudo nginx -t && sudo systemctl reload nginx

Editor’s Note

実際の報告例として、Nginx 公式の課題管理システムのチケットがあります(trac #2163、2021年)。本番環境で 499 が出続け、毎日 Nginx を再起動してしのいでいるという報告に対し、開発側からの回答で、499 で閉じられたリクエストエラーログには info レベルでしか記録されないこと、エラーログのレベルを上げて確認すべきは上流とのやり取りのエラータイムアウトなど)であることが説明されています。報告者がデバッグログを有効にしたところ、「client prematurely closed connection, so upstream connection is closed too while reading response header from upstream」(クライアントが先に接続を閉じたため、上流への接続も閉じた)という記録が確認され、問題は Nginx ではなく特定の上流サーバーの応答にあると切り分けられていきます。あわせて、応答の速さに期待があるなら proxy_connect_timeoutproxy_read_timeout を実態に合わせて調整すべきという助言も示されています。「エラーログに何も出ないのに 499 が続く」という本記事の診断上の特徴と、原因が上流の遅さに帰着するという典型例の両方が確認できる報告です。

499 は Nginx の異常を示すコードではなく、クライアントが待ちきれなかったという事実の記録です。時間の記録を整え、誰がどれだけ待って諦めたのかを読み取ることが、原因への確実な近道です。


免責事項:本記事の内容は、執筆時点の公開情報をもとに作成したものです。ソフトウェアの仕様は予告なく変更されることがあります。最新の情報は各ツールの公式サポートページをご確認ください。本記事の情報を利用した結果生じたいかなる損害についても、著者および運営者は責任を負いかねます。