- ネットワーク
502エラー(Bad Gateway)の原因と直し方|error.log原文で判定
結論
502 Bad Gateway は、間に立つサーバー(nginx・ロードバランサー・CDN)が、つなぎ先のアプリから「正しい応答を受け取れなかった」ときのエラーです。見ている人は少し待って再読み込み、作る人は中継サーバーの error.log の文言で原因を決めます。
この記事でわかること
- 502の意味と、500・503・504との違い(時間切れは504、落ちているのは502)
- nginx の後ろで PHP-FPM・Gunicorn・Node を止める・壊すなど13通りで502を起こした error.log の原文と原因の対応表
- 見る人向けの対処と、作る人向けの切り分け手順・再送の決まり・自前の画面の出し方
ブラウザが直接アプリと話しているわけではなく、多くのサイトでは間に nginx やロードバランサーが入っています。502は、この「間のサーバー」が、後ろのアプリから受け取ったものを「HTTP の応答として読めない」と判断したときに、代わりに返すエラーです。
flowchart TD
B[ブラウザ] --> G[中継サーバー<br>nginx・LB・CDN]
G --> A[アプリ<br>PHP-FPM・Gunicorn・Node]
A -.->|つながらない<br>途中で切れる<br>壊れた応答| G
G -->|502 Bad Gateway| B
よく調べられることの答えを先にまとめます。
| 知りたいこと | 答え | 詳しい節 |
|---|---|---|
| 502エラーとは | 中継サーバーが、つなぎ先から正しい応答を受け取れなかった | 502 Bad Gatewayとは |
| 502の原因 | アプリが落ちている・ポートやソケット名が違う・途中で切れた・応答が壊れている | 502の原因一覧 |
| 502が出た(見る人) | 少し待って再読み込み。別の回線・シークレットウィンドウで同じなら、サイト側 | 見ている人の対処 |
| 502の直し方(作る人) | 中継サーバーの error.log の文言で原因を決め、アプリ側を直す | error.log原文と原因の対応表 |
| 502 と 504 の違い | 504は時間切れ(応答が来ない)、502は応答が来たが壊れている・つながらない | 500・502・503・504の違い |
| 502 と 503 の違い | 503はサーバー自身が「今は無理」と答えている。502は答えがもらえなかった | 500・502・503・504の違い |
| Keep-Alive で時々502 | アプリが先に接続を閉じている。アプリ側の keep-alive の時間を長くする | Keep-Aliveの切れ目で起きる「時々502」 |
| ALB・Cloudflare の502 | 公式ドキュメントの原因一覧と、どちらが返したかの見分け方 | ALB・Cloudflare・CDNの502 |
| SEO への影響 | 続くとクロールが減り、やがてインデックスから外れる | 502はSEOに影響するか |
確認環境:Ubuntu 24.04.5 LTS の上で、nginx 1.24.0(Ubuntu のパッケージを作業用のフォルダーに展開し、-p・-c で自分の設定ファイルを指定)を 127.0.0.1 のポートに起動しました。つなぎ先は PHP-FPM 8.3.6(UNIX ソケットと TCP の2通り)、Gunicorn 26.2.0+Flask 3.1.3(Python 3.13.18)、Node.js 22.23.4 の http サーバー、壊れた応答を返す自作の TCP サーバー(Python)、HTTPS だけで待つサーバー(Python の ssl)です。応答は curl 8.5.0、ブラウザの表示は Chromium 153.0(playwright 1.63.0・日本語の画面)で確かめました。AWS ALB・Cloudflare の502は公式ドキュメントの範囲で書いています。定義は MDN と RFC 9110、検索への影響は Google 検索セントラルで確かめました(確認日 2026年10月29日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「502エラー(Bad Gateway)の原因と直し方|error.log原文で判定」
502 Bad Gatewayとは(意味と仕組み)
この節では、502の正確な意味と、なぜ「アプリのエラー」ではなく「間のサーバーのエラー」として返るのかが分かります。
502 Bad Gateway は、ゲートウェイまたはプロキシとして動くサーバーが、上流(つなぎ先)のサーバーから不正な応答を受け取ったことを示すステータスコードです。 MDN は次のように説明しています。
HTTP の 502 Bad Gateway はサーバーエラーレスポンスステータスコードで、ゲートウェイまたはプロキシーとして機能しているサーバーが上流のサーバーから不正なレスポンスを受け取ったことを示しています。
仕様は RFC 9110 の 15.6.3 で、「ゲートウェイまたはプロキシとして動作中に、リクエストを処理するために接続した内側のサーバーから無効な応答を受け取った」と定義されています。
ここで大事なのは、502を返しているのはアプリではなく、間に立つサーバーだという点です。手元の nginx が返した502の画面には nginx/1.24.0 (Ubuntu) と書いてあり、アプリ(Flask や PHP)は何も返していません。アプリが自分でエラーを返せたなら500や503になり、アプリが何も返せない・読めないものを返したときに、nginx が代わりに502を書いています。
502になる(答えがもらえない・読めない)
アプリが落ちている・起動していない
ポート・ソケット名・権限が違ってつながらない
処理の途中で接続が切れた
応答のヘッダーが壊れている・大きすぎる
502にならない(アプリが答えている)
アプリの例外 → 500(アプリが返す)
アプリが過負荷と答える → 503
決めた時間までに何も来ない → 504
本文が大きいだけ → 200(手元で 3MB を確認)
「ゲートウェイ」と「プロキシ」
どちらも「クライアントとサーバーの間に立って、代わりに通信する」ものです。リバースプロキシの nginx、AWS の ALB、Cloudflare のような CDN、Kubernetes の Ingress、API Gateway は全部この立場で、どれも後ろの応答を読めなければ502を返します。
500・502・503・504の違い(時間切れは504、落ちているのは502)
この節では、5xx の中で502をどう区別するかが分かります。
「誰が、何を理由に返したか」で分けると迷いません。
| コード | 返す人 | 意味 | 手元での例 |
|---|---|---|---|
| 500 Internal Server Error | アプリ(または中継) | 処理の中で予期しないエラー | PHP の Fatal error は500で返った(PHP-FPM が自分でエラー応答を作った) |
| 502 Bad Gateway | 中継サーバー | つなぎ先から正しい応答を受け取れない | アプリを止める・途中で落とす・壊れた応答を返す |
| 503 Service Unavailable | アプリ・中継のどちらも | 今は処理できない(過負荷・メンテナンス) | nginx の全 upstream が「一時停止」扱いになったとき |
| 504 Gateway Time-out | 中継サーバー | 決めた時間までに応答が来ない | 自作サーバーが90秒黙ると、nginx は60秒で504 |
電話にたとえると
見分ける問い:「応答が来なかった(504)のか、来たが読めなかった・つながらなかった(502)のか」
MDN は、上流が有効なエラー応答を返したときは、ゲートウェイは502を返さずにそのまま中継すべきであり、HTTP の応答を受け取れなかったときは504を返す、と説明しています。504の原因と直し方は、関連記事「504 Gateway Time-outの原因と直し方|502・503との違いと実例」で扱っています。
502エラーの原因一覧(見る人・作る人別)
この節では、502になる原因を、自分の側で直せるものと、サイト側で直すものに分けて一覧で見られます。
502の原因のほとんどはサイト側(つなぎ先のアプリと中継の設定)にあります。 見ている人の側の原因は、VPN・プロキシ・前に保存されたキャッシュなど、限られています。
| 原因 | どちら側 | 手元で確かめた |
|---|---|---|
| アプリが起動していない・落ちた | サイト側 | Node・Gunicorn・PHP-FPM を止めた |
| ポート番号・ソケットのパスの綴りが違う | サイト側 | PHP-FPM のソケット名を1文字変えた |
| UNIX ソケットの権限が無い | サイト側 | ソケットを 0600 にした |
| 処理の途中でアプリのプロセスが落ちた | サイト側 | Gunicorn・Node・PHP を途中で kill |
| 応答ヘッダーが大きすぎる(Cookie など) | サイト側 | 12KB の Set-Cookie・PHP の header() |
| 応答が HTTP として壊れている | サイト側 | ヘッダー名に空白・ステータス行なし |
| HTTP と HTTPS の取り違え | サイト側 | https のポートに http で・その逆 |
| Keep-Alive の切れ目に当たった | サイト側 | 再利用した接続をアプリが閉じた |
| デプロイ直後・再起動中 | サイト側 | 上と同じ「つながらない」 |
| 過負荷でアプリが応答を作れない | サイト側 | (公式ドキュメント) |
| VPN・プロキシ・拡張機能が間に入っている | 見る人の側 | (MDN の記述) |
| ブラウザのキャッシュ・DNS の前の情報 | 見る人の側 | (上位記事の内容。発生は稀) |
502エラーが出たときの対処(見ている人向け)
この節では、Web サイトを見ていて502が出たときに、自分でできることと、できないことが分かります。
502は、ほとんどの場合サイト側の一時的な障害です。 数十秒から数分待って再読み込みすると直ることが多く、それでも続くときに、自分の側の要因を1つずつ外していきます。
30秒ほど待って再読み込みする
デプロイや再起動の最中、アプリの一時的な落ちなら、短い時間で戻る。連打すると、復旧中のサーバーに負荷をかける。
別のページ・別の回線・別の端末で開く
同じサイトの別のページも502なら、サイト全体が落ちている。スマホの回線で開いて直るなら、自分の回線・VPN・プロキシが関係している。
シークレットウィンドウで開く
拡張機能と保存済みの情報を使わずに開ける。これで直るなら、拡張機能(広告ブロック・VPN 系)やキャッシュが原因。
VPN・プロキシを切る
MDN は、クライアントの側に原因がある例外として、VPN などの独自のネットワーク設定を挙げている。会社のネットワークなら、プロキシの設定を疑う。
サイトの稼働状況を確かめ、続くなら待つ
サービスの公式の稼働状況ページや SNS の告知を見る。自分の側で直せることは上の4つまでで、それ以上はサイト側の復旧を待つ。
- フォームの送信直後に502が出たときは、もう一度送る前に、送信が済んでいないかを確かめます。 502はアプリが応答を返せなかったという意味で、処理が途中まで進んでいた可能性があります(注文・決済・投稿など)。
- ルーターの再起動や DNS キャッシュの削除は、上位の記事でよく勧められていますが、502は「サーバー側の中継が後ろとやり取りできない」エラーなので、自分の回線が原因になるのは VPN・プロキシを通しているときが主です。
Chromeでの502の見え方(HTTP ERROR 502)
この節では、502のときにブラウザに何が表示され、表示から何が分かるかが分かります。
画面は、中継サーバーが本文を返したかどうかで変わります。 手元で3通りの502と、502になりきれなかった壊れた応答を Chromium(日本語の画面)で開いた結果です。
| 返した場所 | ブラウザに見えた表示 |
|---|---|
| nginx の既定の502ページ | 502 Bad Gateway/nginx/1.24.0 (Ubuntu) |
| 本文の無い502 | このページは動作していません/127.0.0.1 では現在このリクエストを処理できません。/HTTP ERROR 502 |
| 自前のメンテナンス画面(error_page 502) | ただいまメンテナンス中です。しばらく時間をおいて、もう一度お開きください。 |
| つなぎ先が HTTP でない文字列を返した | このページは動作していません/127.0.0.1 から無効な応答が送信されました。/ERR_INVALID_HTTP_RESPONSE |
- 「HTTP ERROR 502」の画面は、サーバーが本文の無い502を返したときに Chrome が代わりに出す画面です。 画面の下にソフトの名前(nginx など)があれば、そのソフトが返した502です。Cloudflare の画面なら Cloudflare が、ALB なら ALB が返しています。
- Apache の mod_proxy が返す既定の502ページには「The proxy server received an invalid response from an upstream server.」という文言が入っています(Apache に同梱のエラー文書)。
- 最後の行の ERR_INVALID_HTTP_RESPONSE は、nginx が「ステータス行の無い応答」を HTTP/0.9 とみなしてそのまま通したため、ブラウザが502ではなく「無効な応答」と判断したものです。詳しくは「応答が壊れている」の節で扱います。
nginxのerror.log原文と502の原因の対応表
この節では、手元で起こした502の error.log の文言と、原因・直し方の対応が分かります。この表が、この記事の中心です。
nginx は502を返すとき、必ず error.log に「何をしている最中に、何が起きたか」を書きます。 文言の前半(connect() failed・upstream prematurely closed など)が原因、後半(while connecting to upstream・while reading response header)がどの段階かを示します。
実験の構成(nginx の後ろに PHP-FPM・Gunicorn・Node)
nginx 1.24.0 を 127.0.0.1:46201 に起動し、後ろに5種類のつなぎ先を置きました(ログやパスの行を省いています)。
log_format up '$remote_addr [$time_local] "$request" $status '
'up=$upstream_addr ust=$upstream_status urt=$upstream_response_time';
access_log /path/to/access.log up;
upstream app_keep2 { server 127.0.0.1:46216; keepalive 8; } # Keep-Alive を使う中継
upstream app_pair { server 127.0.0.1:46210; server 127.0.0.1:46215; }
server {
listen 127.0.0.1:46201;
location /py/ { proxy_pass http://127.0.0.1:46210/; } # Gunicorn + Flask
location /node/ { proxy_pass http://127.0.0.1:46211/; } # Node.js
location /raw/ { proxy_pass http://127.0.0.1:46212/; } # 壊れた応答を返すサーバー
location /tls/ { proxy_pass http://127.0.0.1:46213/; } # HTTPS だけのサーバーに http:// で
location /tls2/ { proxy_pass https://127.0.0.1:46210/; } # HTTP だけのサーバーに https:// で
location /keep2/ { proxy_http_version 1.1; proxy_set_header Connection "";
proxy_pass http://app_keep2/; }
location /pair/ { proxy_pass http://app_pair/; }
location ~ ^/php/(.*\.php)$ { # PHP-FPM(UNIX ソケット)
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /path/to/www/$1;
fastcgi_pass unix:/path/to/run/php.sock;
}
}
access.log に $upstream_addr・$upstream_status・$upstream_response_time を足しているのがポイントで、「どのつなぎ先が何を返したか」が1行で分かります。502の調査では、この3つがあるかどうかで速さが変わります。
| error.log の文言(原文) | 起こした操作 | 原因と直し方 |
|---|---|---|
connect() failed (111: Connection refused) while connecting to upstream |
Node・PHP-FPM(TCP)を止めた | そのポートで誰も待っていない。アプリを起動する・ポート番号を合わせる |
connect() to unix:/…/php-fpm.sock failed (2: No such file or directory) |
ソケットの綴りを変えた/PHP-FPM を止めた | ソケットのパスが違う、またはアプリが止まっている。ls -l でソケットの有無を見る |
connect() to unix:/…/php-perm.sock failed (13: Permission denied) |
ソケットを root 0600 にした | nginx の worker(手元では nobody)がソケットを読み書きできない。listen.owner・listen.group・listen.mode を合わせる |
upstream prematurely closed connection while reading response header from upstream |
Gunicorn・Node を処理中に落とした/何も返さず閉じた | 応答を返す前にアプリのプロセスが終わった。アプリのログ(落ちた理由・OOM・ワーカーの kill)を見る |
recv() failed (104: Connection reset by peer) while reading response header from upstream |
PHP のワーカーを SIGKILL/HTTPS のポートに http で接続 | 相手が接続を強制的に切った。PHP-FPM のログの「exited on signal」、プロトコルの取り違えを見る |
upstream sent too big header while reading response header from upstream |
12KB の Set-Cookie・PHP の header() | 応答ヘッダーがバッファ(既定 4k か 8k)に入らない。proxy_buffer_size・fastcgi_buffer_size を大きくする。Cookie を減らす |
upstream sent invalid header: "X-Bad\x20..." while reading response header from upstream |
ヘッダー名に空白を入れた | アプリが HTTP として不正なヘッダーを返している。アプリ側のヘッダー生成を直す |
upstream sent no valid HTTP/1.0 header while reading response header from upstream |
ステータス行の無い文字列を返した | HTTP でないものが返っている(別のサービスのポート、デバッグ出力)。つなぎ先を見直す |
SSL_do_handshake() failed (SSL: error:0A00010B:SSL routines::wrong version number) while SSL handshaking to upstream |
HTTP だけのポートに https:// で接続 | proxy_pass の https:// と、つなぎ先の待ち受けが合っていない |
upstream server temporarily disabled while connecting to upstream(warn) |
2台のうち1台を止めた | 失敗したサーバーを一時的に外した印。残りで続いていれば502にはならない |
実際のログは次の形です(日時を省いています)。upstream: の後ろに、どのつなぎ先に何を送ろうとしたかが書かれているので、ここを最初に見ます。
[error] 25596#25596: *33 connect() failed (111: Connection refused) while connecting to upstream, client: 127.0.0.1, server: , request: "GET /node/ HTTP/1.1", upstream: "http://127.0.0.1:46211/", host: "127.0.0.1:46201"
[crit] 25596#25596: *49 connect() to unix:/dev/shm/t46/run/php-fpm.sock failed (2: No such file or directory) while connecting to upstream, client: 127.0.0.1, server: , request: "GET /phpbad/index.php HTTP/1.1", upstream: "fastcgi://unix:/dev/shm/t46/run/php-fpm.sock:", host: "127.0.0.1:46201"
[error] 25596#25596: *25 upstream prematurely closed connection while reading response header from upstream, client: 127.0.0.1, server: , request: "GET /py/crash HTTP/1.1", upstream: "http://127.0.0.1:46210/crash", host: "127.0.0.1:46201"
[error] 25596#25596: *21 upstream sent too big header while reading response header from upstream, client: 127.0.0.1, server: , request: "GET /py/bighdr HTTP/1.1", upstream: "http://127.0.0.1:46210/bighdr", host: "127.0.0.1:46201"
1while connecting なら「つながらない」
アプリが起動していない、ポート・ソケット名・権限の問題。アプリ側のログには何も残らない(リクエストが届いていない)。
2while reading response header なら「つながったが壊れた」
処理の途中で落ちた、ヘッダーが大きい・不正。アプリ側のログに手がかりが残る。
3[crit] は UNIX ソケット、[error] は TCP
手元では、ソケットの「無い・権限なし」は crit、TCP の接続拒否は error の重さで記録された。grep するときは両方を見る。
つなぎ先が落ちている・つながらない502(connect() failed)
この節では、いちばん多い「アプリが起動していない・ポートが違う」の502と、その確かめ方が分かります。
Node のプロセスを終了させてから開くと、即座に502になりました。curl -v の原文です(Date を省いています)。
$ curl -v http://127.0.0.1:46201/node/
> GET /node/ HTTP/1.1
> Host: 127.0.0.1:46201
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 502 Bad Gateway
< Server: nginx/1.24.0 (Ubuntu)
< Content-Type: text/html
< Content-Length: 166
< Connection: keep-alive
<
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.24.0 (Ubuntu)</center>
</body>
</html>
access.log($upstream_addr などを足した形式)には、つなぎ先と、そこから返った(返らなかった)ステータスが残ります。
127.0.0.1 [29/Oct/2026:22:37:02 +0900] "GET /node/ HTTP/1.1" 502 up=127.0.0.1:46211 ust=502 urt=0.000
127.0.0.1 [29/Oct/2026:22:41:26 +0900] "GET /phptcp/index.php HTTP/1.1" 502 up=127.0.0.1:46220 ust=502 urt=0.000
urt=0.000(つなぎ先の応答時間が0秒)は「つながってすらいない」印です。504なら、ここに proxy_read_timeout の値(既定60秒)に近い数字が入ります。
確かめる順番
- 1アプリが動いているか
ss -ltnp | grep 46211で、そのポートで待っているプロセスがあるか。無ければ起動する・落ちた理由をアプリのログで見る - 2nginx の設定とポートが一致しているかproxy_pass の番号と、アプリの --bind・listen の番号。設定を変えたら
nginx -t→ reload - 3中継を通さずに直接開けるか
curl http://127.0.0.1:46211/が200なら、問題は nginx とアプリの間。これも失敗ならアプリ側 - 4UNIX ソケットなら、ファイルの有無と権限
ls -l /run/php/php8.3-fpm.sock。nginx の worker のユーザーが読み書きできる owner・group・mode か - 5localhost と 127.0.0.1 の違いlocalhost が ::1(IPv6)にも解決される環境では、アプリが 127.0.0.1 だけで待っていると、::1 への接続が拒否される。proxy_pass は 127.0.0.1 で書く
UNIX ソケットの権限は、手元で chmod 660 と chown nobody:nogroup(nginx の worker のユーザー)にしたところ、502が200になりました。PHP-FPM の設定では listen.owner・listen.group・listen.mode で決めます。
処理の途中で落ちる502(upstream prematurely closed)
この節では、つながってはいるが、アプリが応答を返す前に終わってしまうときの502が分かります。
「prematurely closed(早すぎる切断)」は、アプリが応答ヘッダーを書き切る前に接続を閉じた、という意味です。 手元では3通りで起こしました。
| やったこと | nginx の error.log | アプリ側のログ |
|---|---|---|
| Flask の処理の中で自分のプロセスに SIGKILL | upstream prematurely closed connection | Gunicorn:Booting worker with pid(ワーカーを作り直した) |
| Node の処理の中で process.exit(1) | upstream prematurely closed connection | (プロセスごと終わるので何も残らない。次からは Connection refused) |
| PHP の中で自分のワーカーを kill -9 | recv() failed (104: Connection reset by peer) | PHP-FPM:child 25553 exited on signal 9 (SIGKILL) after 37.559456 seconds from start |
PHP-FPM のログの原文です。「どのプールの子プロセスが、何のシグナルで終わったか」が残るので、OOM(メモリ不足)で kill されたときの手がかりになります。
[29-Oct-2026 22:37:04] WARNING: [pool www] child 25553 exited on signal 9 (SIGKILL) after 37.559456 seconds from start
[29-Oct-2026 22:37:04] NOTICE: [pool www] child 25944 started
Gunicorn のワーカーのタイムアウトは502か500か
「Gunicorn の timeout より長い処理で502になる」という説明をよく見ます。手元の Gunicorn 26.2.0 で --timeout 2 にして5秒かかる処理を呼ぶと、結果は500でした。Gunicorn はワーカーを強制終了する前に、処理中のリクエストへ500を返してから終了しており、nginx の error.log には何も出ません。
[2026-10-29 22:37:01 +0900] [25574] [CRITICAL] WORKER TIMEOUT (pid:25579)
[2026-10-29 22:37:01 +0900] [25579] [ERROR] Error handling request GET /slow
...
File "/dev/shm/t46/venv/lib/python3.13/site-packages/gunicorn/workers/base.py", line 199, in handle_abort
sys.exit(1)
SystemExit: 1
[2026-10-29 22:37:01 +0900] [25579] [INFO] Worker exiting (pid: 25579)
版によって、ワーカーが応答を返さずに終わり502になる動きもあります。502か500かにかかわらず、Gunicorn のログに WORKER TIMEOUT が出ていれば、処理時間が --timeout(既定30秒)を超えたのが原因です。時間のかかる処理をなくすか、--timeout を延ばすか、非同期のワーカーに変えます。
応答が大きすぎる・壊れている502(too big header・invalid header・HTTPS取り違え)
この節では、アプリは動いているのに、返した応答を nginx が読めずに502になる3つの場合と直し方が分かります。
応答ヘッダーが大きすぎる(upstream sent too big header)
nginx は、応答の最初の部分(ヘッダー)を proxy_buffer_size(既定は 4k または 8k、環境のメモリページ1枚分)のバッファで読みます。入りきらないと、応答を「無効」とみなして502にします。 公式ドキュメントにも、このサイズを超えた応答は invalid と扱う、と書かれています。
Flask で 12KB の Set-Cookie を返したときの結果です。
@app.get("/bighdr")
def bighdr():
r = Response("big header\n")
r.headers["Set-Cookie"] = "session=" + "x" * 12000 # ヘッダーだけで 12KB
return r
$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:46201/py/bighdr
502
error.log: upstream sent too big header while reading response header from upstream, ... upstream: "http://127.0.0.1:46210/bighdr"
バッファを大きくした location では、同じ応答が200になりました。
location /bigok/ {
proxy_buffer_size 32k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_pass http://127.0.0.1:46210/;
}
# PHP-FPM(FastCGI)のときは名前が変わる
location ~ \.php$ {
fastcgi_buffer_size 32k;
fastcgi_buffers 8 32k;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:46201/bigok/bighdr
200
$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:46201/phpbig/bighdr.php # PHP の header() で 12KB
200
よく出る場面
セッションを Cookie に丸ごと入れる、Set-Cookie を何十個も返す、SSO のトークンを Cookie に入れる、デバッグ用のヘッダー。
本文の大きさは関係ない
まず減らす
Cookie に入れるのはセッション ID だけにし、中身はサーバーに置く。不要なヘッダーを消す。
根本の直し方
それでも要るなら広げる
proxy_buffer_size(FastCGI は fastcgi_buffer_size)を 16k〜32k に。proxy_buffers・busy_buffers_size もつじつまを合わせる。
nginx -t で確かめる
ALB は 32KB が上限
AWS の公式ドキュメントは、ターゲットの応答ヘッダー全体が 32KB を超えると502になる、と明記している。
設定では変えられない
本文が大きいだけなら502にはなりません。手元で 3MB の本文を返したときは200で、バッファに入りきらない分は一時ファイルに書かれます。
応答が HTTP として壊れている(invalid header・no valid HTTP/1.0 header)
HTTP として壊れた応答を返す TCP サーバーを作り、パスごとに違うものを返しました。
| つなぎ先が返したもの | 結果 | error.log の文言 |
|---|---|---|
HTTP/1.1 200 OK\r\nX-Bad Header: a\r\n\r\nok(ヘッダー名に空白) |
502 | upstream sent invalid header: "X-Bad\x20..." |
HTTP/1.1 200 OK\r\nContent-Ty(ヘッダーの途中で切れる) |
502 | upstream prematurely closed connection |
| 何も返さずに閉じる | 502 | upstream prematurely closed connection |
hello, this is not http(HTTP でない文字列) |
HTTP/0.9 として通した | upstream sent no valid HTTP/1.0 header |
<html>no status line</html>(ステータス行なし) |
HTTP/0.9 として通した | upstream sent no valid HTTP/1.0 header |
最後の2つは意外な動きで、nginx は「ステータス行の無い応答」を HTTP/0.9(ヘッダーの無い最初期の HTTP)とみなし、502にせず、受け取った文字列をそのまま本文としてクライアントに流しました。curl 8.5.0 は既定で HTTP/0.9 を受け付けないので、次のエラーになります。
$ curl http://127.0.0.1:46201/raw/garbage
curl: (1) Received HTTP/0.9 when not allowed
$ curl --http0.9 http://127.0.0.1:46201/raw/garbage
hello, this is not http
Chromium では「127.0.0.1 から無効な応答が送信されました。ERR_INVALID_HTTP_RESPONSE」になりました。ブラウザでは502ではなく ERR_INVALID_HTTP_RESPONSE、error.log には no valid HTTP/1.0 header、という組み合わせを見たら、proxy_pass の先が HTTP を話さないサービス(別のポート・デバッグ用の出力・HTTPS)を指しています。
HTTP と HTTPS の取り違え(SSL_do_handshake・Connection reset)
proxy_pass の http:// と https:// を取り違えたときは、次の2通りの文言になります。
2通りを試しました。
| 設定 | 結果 | error.log の文言 |
|---|---|---|
HTTPS だけで待つサーバーに proxy_pass http://… |
502(即座) | recv() failed (104: Connection reset by peer) while reading response header from upstream |
HTTP だけで待つ Gunicorn に proxy_pass https://… |
502(2.5秒後) | SSL_do_handshake() failed (SSL: error:0A00010B:SSL routines::wrong version number) while SSL handshaking to upstream |
「wrong version number」は、TLS の握手を始めたのに相手が平文の HTTP で答えた、という意味です。 逆に、TLS でしか話さないサーバーに平文を送ると、相手が接続を切って Connection reset になりました(サーバーの実装によっては「no valid HTTP/1.0 header」になることもあります)。どちらも、proxy_pass のスキームと、つなぎ先の待ち受けを合わせれば直ります。AWS の公式ドキュメントも、ALB の502の原因の1つに「ターゲットへの接続時の SSL ハンドシェイクのエラー」を挙げています。
Keep-Aliveの切れ目で起きる「時々502」
この節では、ふだんは正常なのに、数百回に1回だけ502になる現象の仕組みと直し方が分かります。
中継サーバーがアプリとの接続を使い回している(Keep-Alive)とき、アプリ側が「もう使われない」と判断して接続を閉じるのと、中継が次のリクエストを流すのが同時になると、中継は「送ったのに何も返らず閉じられた」として502にします。 AWS の公式ドキュメントは、ALB の502の原因として「ターゲットが未完了のリクエストの処理中に TCP RST か FIN で接続を閉じた」を挙げ、ターゲットの keep-alive の時間がロードバランサーのアイドルタイムアウトより短くないか確かめるよう書いています。
手元では、同じ接続での2回目のリクエストに返事をせず閉じるサーバーを作り、nginx の upstream … keepalive 8; で中継しました。
$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:46201/keep2/ # 1回目
200
$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:46201/keep2/ # 2回目(使い回した接続が閉じられる)
200
error.log: upstream prematurely closed connection while reading response header from upstream, ... request: "GET /keep2/ HTTP/1.1"
access.log: "GET /keep2/ HTTP/1.1" 200 up=127.0.0.1:46216, 127.0.0.1:46216 ust=502, 200 urt=0.000, 0.000
$ curl -s -o /dev/null -w '%{http_code}\n' -X POST -d a=1 http://127.0.0.1:46201/keep2/ # POST で同じこと
502
access.log: "POST /keep2/ HTTP/1.1" 502 up=127.0.0.1:46216 ust=502 urt=0.000
結果は方法によって分かれました。
- GET は200になりました。 error.log には prematurely closed が出ていますが、access.log の
ust=502, 200のとおり、nginx は使い回した接続で失敗すると新しい接続でもう一度送り、2回目が成功しています。 - POST は502でした。 nginx の公式ドキュメントによると、POST・LOCK・PATCH のような冪等でないメソッドは、すでに上流へ送られていれば次のサーバーへ渡されません(1.9.13 以降)。もう一度送ると二重登録になりうるからです。
proxy_next_upstream … non_idempotent;と明示すると再送されますが、二重登録を受け入れられる処理に限ります。 proxy_next_upstream off;にした location では、GET でも502になりました。
「時々502」の直し方
「中継が先に閉じる」順番にすれば、使い回した接続に送った瞬間に閉じられることが無くなる
Node.js の既定の server.keepAliveTimeout は5秒で、ALB の既定のアイドルタイムアウト60秒より短いため、この組み合わせは「時々502」の典型です。Node 側を server.keepAliveTimeout = 65000; のように中継より長くします(headersTimeout もそれより長くします)。
作る人向けの切り分け手順(設定・監視・ログ・再送)
この節では、本番で502の報告を受けたときに何をどの順で見るか、502を減らす設定、監視と再送の決まりが分かります。
- 1誰が502を返したかを決める応答の Server ヘッダー・画面の名前(nginx・cloudflare・awselb)。CDN の画面か、オリジンの画面か
- 2その中継サーバーの error.log を見る上の対応表で文言→原因を決める。while connecting なら「つながらない」、while reading なら「つながったが壊れた」
- 3全部か、一部かaccess.log の $upstream_addr で、特定の1台・特定の URL・特定のメソッド(POST だけ)に偏っていないか
- 4アプリ側のログと時刻を突き合わせるWORKER TIMEOUT・exited on signal・OOM killer・再起動の記録。デプロイの時刻と重なっていないか
- 5中継を通さずに直接呼ぶアプリのポートに curl。200なら中継との間(ポート・ソケット・バッファ・スキーム)、失敗ならアプリ
- 6直したら、同じ手順で再現しないことを確かめるnginx -t → reload → curl -v → error.log に新しい行が出ないこと
502を減らす設定(1)2台に振り分けると、1台が落ちても続く
upstream app_pair に2台登録し、片方(46215)を止めてから3回開いた結果です。
"GET /pair/ HTTP/1.1" 200 up=127.0.0.1:46210 ust=200 urt=0.004
"GET /pair/ HTTP/1.1" 200 up=127.0.0.1:46215, 127.0.0.1:46210 ust=502, 200 urt=0.001, 0.003
"GET /pair/ HTTP/1.1" 200 up=127.0.0.1:46210 ust=200 urt=0.001
error.log:
[error] connect() failed (111: Connection refused) while connecting to upstream, ... upstream: "http://127.0.0.1:46215/"
[warn] upstream server temporarily disabled while connecting to upstream, ... upstream: "http://127.0.0.1:46215/"
2行目で止めた側に当たりましたが、既定の proxy_next_upstream error timeout; によってもう1台に送り直され、クライアントには200が返りました。その後 temporarily disabled として一時的に外されます(既定では max_fails=1 fail_timeout=10s)。全部が外れると、nginx は502ではなく503を返すことがあります。
502を減らす設定(2)自前の画面と Retry-After を返す
location / {
proxy_pass http://127.0.0.1:46219/;
proxy_intercept_errors on; # 上流の 3xx 以上を error_page で扱う
error_page 502 503 504 /maintenance.html;
}
location = /maintenance.html {
internal;
add_header Retry-After 60 always; # 60 秒後に試すよう伝える
}
$ curl -si http://127.0.0.1:46202/
HTTP/1.1 502 Bad Gateway
Server: nginx/1.24.0 (Ubuntu)
Content-Type: text/html
Content-Length: 192
Retry-After: 60
<!doctype html><meta charset="utf-8"><title>メンテナンス中</title><p>ただいまメンテナンス中です。しばらく時間をおいて、もう一度お開きください。</p>
ステータスは502のままで、本文だけ自前の画面にしています。502を200に書き換えてはいけません。 監視・CDN・検索エンジンはステータスで判断するため、200にすると障害に気づけず、エラー画面がキャッシュされます。
監視で拾うもの
- access.log に
$upstream_addr・$upstream_status・$upstream_response_timeを入れておくと、「どの1台が」「何を返して」「何秒で」が1行で分かります。 - 5xx の割合だけでなく、502と504を分けて数えます。502が増えたらアプリの落ち・設定の不一致、504が増えたら遅さ、と対処が違います。
- error.log の
connect() failedとprematurely closedを別々に数えると、「起動していない」と「途中で落ちる」を区別できます。
クライアント(呼ぶ側)の再送の決まり
| 受け取ったコード | 再送するか | 補足 |
|---|---|---|
| 502 | GET など冪等な要求は、間を空けて数回まで | POST は、処理が済んだ可能性があるので、冪等キーが無ければ再送しない |
| 503 | Retry-After があればその秒数を待って | 無ければ指数バックオフ |
| 504 | 502と同じ | 処理が途中まで進んでいる可能性は502より高い |
| 500 | 原則しない | 同じ入力なら同じ結果になることが多い |
再送の回数と間隔、失敗が続いたときに呼ぶのをやめる仕組みは、関連記事「サーキットブレーカーとは|3状態の実装とリトライ可否の一覧表」で扱っています。
判断表:見たもの→原因→直す場所
ここまでの内容を1枚にまとめます。
| 見えたもの | 原因の見当 | 直す場所 |
|---|---|---|
| error.log に connect() failed (111) | アプリが起動していない・ポート違い | アプリの起動・proxy_pass の番号 |
| error.log に unix:… (2: No such file) | ソケットのパス違い・アプリ停止 | fastcgi_pass・listen のパス |
| error.log に unix:… (13: Permission denied) | ソケットの権限 | listen.owner・group・mode |
| prematurely closed が、デプロイ直後だけ | 再起動中に流した | ヘルスチェック・ローリング更新 |
| prematurely closed が、特定の URL だけ | その処理でプロセスが落ちる(OOM・タイムアウト) | アプリのログ・ワーカーの timeout |
| prematurely closed が、まれに・POST で | Keep-Alive の切れ目 | アプリの keep-alive の時間を中継より長く |
| too big header | Cookie・ヘッダーが大きい | Cookie を減らす・buffer_size |
| invalid header・no valid HTTP/1.0 header | HTTP でない・不正なヘッダー | つなぎ先の確認・アプリのヘッダー生成 |
| SSL_do_handshake・wrong version number | http/https の取り違え | proxy_pass のスキーム |
| Cloudflare のロゴ無しの白い502 | Cloudflare 側(圧縮の不具合など) | オリジンの gzip を切って切り分け |
| ALB の502で、アプリのログには何も無い | 接続時の RST・ヘッダー 32KB 超・keep-alive | ターゲットの keep-alive・ヘッダー・SG |
ALB・Cloudflare・CDNの502(公式ドキュメントの範囲)
この節では、nginx ではなくクラウドのロードバランサーや CDN が502を返すときの原因と、どちらが返したかの見分け方が分かります。
AWS Application Load Balancer
AWS の公式ドキュメント(Troubleshoot your Application Load Balancers)は、HTTP 502 の原因として次を挙げています。
- 接続の確立時に、ターゲットから TCP RST を受け取った。
- 接続の確立時に、ターゲットから予期しない応答(ICMP の到達不能など)を受け取った。セキュリティグループで ALB のサブネットからターゲットのポートへの通信が許可されているか確かめる。
- 未完了のリクエストを送っている最中に、ターゲットが TCP RST か FIN で接続を閉じた。ターゲットの keep-alive の時間がロードバランサーのアイドルタイムアウトより短くないか確かめる。
- ターゲットの応答が不正な形か、無効な HTTP ヘッダーを含む。
- ターゲットの応答ヘッダー全体が 32KB を超えた。
- 登録解除中のターゲットで、処理中のリクエストが登録解除の遅延時間を超えた。
- ターゲットが Lambda のとき:本文が 1MB を超えた、タイムアウトまでに応答しなかった、エラーを返した、スロットリングされた。
- ターゲットへの接続時に SSL ハンドシェイクのエラーが起きた。
この記事で nginx で起こした「途中で閉じる」「ヘッダーが不正」「ヘッダーが大きい」「TLS の取り違え」「Keep-Alive の切れ目」と、そのまま対応しています。
Cloudflare
Cloudflare の公式ドキュメント(Error 502/504)は、502・504を「Cloudflare がオリジンの Web サーバーに接続できない」エラーとして、原因を2つに分けています。
| どちらが返したか | 見分け方 | 対処 |
|---|---|---|
| オリジン(いちばん多い) | Cloudflare のロゴの付いたエラー画面 | オリジンの過負荷・落ち・ネットワークを調べる。ホスティング事業者に相談 |
| Cloudflare 自身 | ロゴの無い白い画面 | オリジンの gzip 圧縮の不具合(Content-Length の不一致・壊れた圧縮データ)を疑い、圧縮を切って切り分ける |
問い合わせには、発生した日時とタイムゾーン、URL、/cdn-cgi/trace の出力を添えるよう書かれています。Cloudflare Tunnel では、cloudflared がオリジンのサービスに到達できないときに502になります。
Kubernetes の Ingress・API Gateway
Ingress(nginx ingress controller など)も中身は nginx なので、error.log の文言はこの記事の表と同じです。Pod が Ready になる前にトラフィックが流れると connect() failed、Pod の終了時に接続が切れると prematurely closed になります。readiness probe と、終了時の猶予(preStop・terminationGracePeriodSeconds)で減らします。
502はSEOに影響するか
この節では、502が続いたときに検索でどう扱われるかが分かります。
短い障害なら影響は小さく、続くとクロールが減り、やがてインデックスから外れます。 Google 検索セントラル(2026年3月6日更新)は、5xx と 429 はクローラーに一時的にクロールの速度を落とさせ、5xx を返した URL の内容は無視され、登録済みの URL も最終的には削除される、と説明しています。サーバーが 2xx を返すようになれば、クロールの頻度は徐々に戻ります。
- メンテナンスで意図的に止めるときは、502ではなく503+Retry-After を返します。中継の後ろを止めると自然に502になりますが、上の
error_pageの設定で503に揃えられます。 - Search Console の「ページのインデックス登録」や「クロールの統計情報」で、5xx の増減を見ます。
よくある質問
Q. 502 Bad Gateway とは何ですか? A. 間に立つサーバー(nginx・ロードバランサー・CDN)が、つなぎ先のアプリから正しい応答を受け取れなかったことを示すステータスコードです。アプリが落ちている、つながらない、途中で切れた、応答が壊れている、のどれかです。
Q. 502エラーは自分のせいですか? A. ほとんどはサイト側の原因です。自分の側が関係するのは、VPN・プロキシ・拡張機能を通しているときが主で、シークレットウィンドウや別の回線で同じなら、サイト側の障害です。
Q. 502エラーはどれくらい待てば直りますか? A. 決まりはありませんが、デプロイや再起動が原因なら数十秒から数分で戻ります。数時間続くときは、サイトの稼働状況の告知を見ます。
Q. 502と504の違いは何ですか? A. 504は「決めた時間までに応答が来なかった」、502は「応答が来たが読めなかった、またはつながらなかった」です。nginx の error.log で、504は upstream timed out、502は connect() failed や prematurely closed になります。
Q. 502と503の違いは何ですか? A. 503はサーバー自身が「今は処理できない」と答えています(メンテナンス・過負荷)。502は答えそのものがもらえていません。nginx は、登録した全てのつなぎ先が一時的に外れると503を返すことがあります。
Q. nginx の error.log に何も出ないのに502になります。 A. その nginx より手前(CDN・ロードバランサー・別の nginx)が返しています。応答の Server ヘッダーや画面の名前で、誰が返したかを先に決めます。
Q. WordPress で502が出ます。 A. 多くは PHP-FPM が止まっている・ソケットのパスが違う・PHP のワーカーがメモリ不足で落ちる、のどれかです。nginx の error.log の文言を上の表で見て、PHP-FPM のログの exited on signal も確かめます。プラグインの切り分けは、その後です。
Q. Keep-Alive を切れば「時々502」は直りますか? A. 直りますが、接続を毎回作り直すので遅くなります。アプリ側の keep-alive の時間を中継側より長くするのが、速さを保ったままの直し方です。
Q. 502のときに nginx は自動で再送しますか? A. 既定(proxy_next_upstream error timeout)では、GET などの冪等な要求は次のつなぎ先へ送り直します。POST などの冪等でない要求は、すでに送っていれば送り直しません(公式ドキュメント。手元でも POST だけ502になりました)。
Q. 502の画面を自前のデザインにできますか?
A. nginx なら proxy_intercept_errors on; と error_page 502 /maintenance.html; で本文を差し替えられます。ステータスは502のままにし、200に変えないようにします。
Q. Gunicorn のタイムアウトは502ですか500ですか? A. 手元の Gunicorn 26.2.0 では、WORKER TIMEOUT のときに500が返りました。版によってはワーカーが応答せずに終わり502になります。どちらでも Gunicorn のログに WORKER TIMEOUT が残るので、そこで判断します。
Q. 502を返した nginx の後ろのアプリのログには何が残りますか? A. 「つながらない」(connect() failed)ならリクエストが届いていないので何も残りません。「途中で落ちた」ならプロセスの終了・再起動の記録が残ります。アプリのログに無いこと自体が、原因の手がかりです。
まとめ
- 502 Bad Gateway は、中継するサーバーが、つなぎ先から正しい応答を受け取れなかったときに返すエラー。返しているのはアプリではなく、nginx・ロードバランサー・CDN。
- 見る人は、少し待って再読み込み→別の回線・シークレットウィンドウ→VPN・プロキシを切る、まで。それでも出るならサイト側の復旧を待つ。
- 作る人は、中継サーバーの error.log の文言で原因を決める。connect() failed は「つながらない」、prematurely closed は「途中で落ちた」、too big header は「ヘッダーが大きい」、SSL_do_handshake は「http/https の取り違え」。
- 「時々502」は Keep-Alive の切れ目。nginx は GET を送り直すが POST は送り直さない。アプリ側の keep-alive の時間を中継より長くして直す。
- 504は時間切れ、503はサーバー自身の「今は無理」、500はアプリのエラー。監視では502と504を分けて数える。
次に読むとよい記事です。中継の設定そのものは関連記事「Nginxリバースプロキシ設定|proxy_passの書き方と502/504対処」、アプリ自身が返す500は関連記事「500エラーとは|Internal Server Errorの原因と直し方を実例で」、認証と権限のエラーは関連記事「401と403の違い|Unauthorized・Forbiddenの原因と直し方」、全部のコードは関連記事「HTTPステータスコード 一覧|全コードの原因と対処・curl実例つき」で扱っています。
参考・出典
確認日はいずれも 2026年10月29日です。
- IETF「RFC 9110 HTTP Semantics」15.6.3 502 Bad Gateway:https://www.rfc-editor.org/rfc/rfc9110#section-15.6.3
- MDN「502 Bad Gateway」:https://developer.mozilla.org/ja/docs/Web/HTTP/Reference/Status/502
- nginx「Module ngx_http_proxy_module」(proxy_buffer_size・proxy_next_upstream・proxy_intercept_errors):https://nginx.org/en/docs/http/ngx_http_proxy_module.html
- nginx「Module ngx_http_upstream_module」(keepalive・max_fails):https://nginx.org/en/docs/http/ngx_http_upstream_module.html
- nginx「Module ngx_http_fastcgi_module」(fastcgi_buffer_size):https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html
- AWS「Troubleshoot your Application Load Balancers」HTTP 502: Bad gateway:https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-troubleshooting.html
- Cloudflare「Error 502/504」:https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-5xx-errors/error-502-504/
- Node.js「http.Server server.keepAliveTimeout」:https://nodejs.org/api/http.html#serverkeepalivetimeout
- Gunicorn「Settings」(timeout・keepalive):https://docs.gunicorn.org/en/stable/settings.html
- PHP「FPM の設定」(listen.owner・listen.group・listen.mode):https://www.php.net/manual/ja/install.fpm.configuration.php
- Google 検索セントラル「HTTP ステータスコード、ネットワーク エラー、DNS エラーが Google 検索に与える影響」:https://developers.google.com/search/docs/crawling-indexing/http-network-errors
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「502エラー(Bad Gateway)の原因と直し方|error.log原文で判定」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。