結論

502 Bad Gateway は、間に立つサーバー(nginx・ロードバランサー・CDN)が、つなぎ先のアプリから「正しい応答を受け取れなかった」ときのエラーです。見ている人は少し待って再読み込み、作る人は中継サーバーの error.log の文言で原因を決めます。

この記事でわかること

  • 502の意味と、500・503・504との違い(時間切れは504、落ちているのは502)
  • nginx の後ろで PHP-FPM・Gunicorn・Node を止める・壊すなど13通りで502を起こした error.log の原文と原因の対応表
  • 見る人向けの対処と、作る人向けの切り分け手順・再送の決まり・自前の画面の出し方

2026年10月29日更新|nginx 1.24.0・PHP-FPM 8.3.6・Gunicorn 26.2.0・Node.js 22.23.4・Chromium 153で実行確認

ブラウザが直接アプリと話しているわけではなく、多くのサイトでは間に 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

電話にたとえると

502受付(中継)が担当者に取り次いだら、話中・不在・ガチャ切り・雑音で何を言っているか分からない
↔
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つずつ外していきます。

1

30秒ほど待って再読み込みする

デプロイや再起動の最中、アプリの一時的な落ちなら、短い時間で戻る。連打すると、復旧中のサーバーに負荷をかける。

まず試す

2

別のページ・別の回線・別の端末で開く

同じサイトの別のページも502なら、サイト全体が落ちている。スマホの回線で開いて直るなら、自分の回線・VPN・プロキシが関係している。

切り分け

3

シークレットウィンドウで開く

拡張機能と保存済みの情報を使わずに開ける。これで直るなら、拡張機能(広告ブロック・VPN 系)やキャッシュが原因。

拡張機能・キャッシュ

4

VPN・プロキシを切る

MDN は、クライアントの側に原因がある例外として、VPN などの独自のネットワーク設定を挙げている。会社のネットワークなら、プロキシの設定を疑う。

VPN・プロキシ

5

サイトの稼働状況を確かめ、続くなら待つ

サービスの公式の稼働状況ページや 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
1

よく出る場面

セッションを Cookie に丸ごと入れる、Set-Cookie を何十個も返す、SSO のトークンを Cookie に入れる、デバッグ用のヘッダー。

本文の大きさは関係ない

2

まず減らす

Cookie に入れるのはセッション ID だけにし、中身はサーバーに置く。不要なヘッダーを消す。

根本の直し方

3

それでも要るなら広げる

proxy_buffer_size(FastCGI は fastcgi_buffer_size)を 16k〜32k に。proxy_buffers・busy_buffers_size もつじつまを合わせる。

nginx -t で確かめる

4

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」の直し方

やってはいけないPOST を含めて無条件に再送する設定(non_idempotent)で502を隠す。二重登録・二重決済の原因になる
→
代わりにアプリ側の keep-alive の時間を、中継側のアイドルの時間より長くする(Node は server.keepAliveTimeout、Gunicorn は --keep-alive、ALB は idle timeout との比較)

「中継が先に閉じる」順番にすれば、使い回した接続に送った瞬間に閉じられることが無くなる

Node.js の既定の server.keepAliveTimeout は5秒で、ALB の既定のアイドルタイムアウト60秒より短いため、この組み合わせは「時々502」の典型です。Node 側を server.keepAliveTimeout = 65000; のように中継より長くします(headersTimeout もそれより長くします)。

作る人向けの切り分け手順(設定・監視・ログ・再送)

この節では、本番で502の報告を受けたときに何をどの順で見るか、502を減らす設定、監視と再送の決まりが分かります。

  1. 1誰が502を返したかを決める応答の Server ヘッダー・画面の名前(nginx・cloudflare・awselb)。CDN の画面か、オリジンの画面か
  2. 2その中継サーバーの error.log を見る上の対応表で文言→原因を決める。while connecting なら「つながらない」、while reading なら「つながったが壊れた」
  3. 3全部か、一部かaccess.log の $upstream_addr で、特定の1台・特定の URL・特定のメソッド(POST だけ)に偏っていないか
  4. 4アプリ側のログと時刻を突き合わせるWORKER TIMEOUT・exited on signal・OOM killer・再起動の記録。デプロイの時刻と重なっていないか
  5. 5中継を通さずに直接呼ぶアプリのポートに curl。200なら中継との間(ポート・ソケット・バッファ・スキーム)、失敗ならアプリ
  6. 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にすると障害に気づけず、エラー画面がキャッシュされます。

13通り手元で起こした502文言は8種類に収まった
4k/8kproxy_buffer_size の既定12KB の Cookie で502
32KBALB の応答ヘッダー上限超えると502(AWS 公式)

監視で拾うもの

  • 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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?