エラーの内容
502 Bad Gateway
これはプロキシ層で発生するエラーです。Nginx、Apache、ロードバランサー、またはCloudflareはリクエストを正常に受信しましたが、そのリクエストをアップストリームに転送しようとした際、有効なレスポンスが返ってきませんでした。プロキシは動いています。問題はバックエンド側にあります。
主な原因:アプリサーバーのクラッシュ、ポートの不一致、アップストリームタイムアウト、またはproxy_passディレクティブの設定ミスです。
ステップ1 — バックエンドが実際に起動しているか確認する
Nginxの設定を触る前に、アップストリームのプロセスが起動しているか確認しましょう。
# Node.js/Python/GunicornアプリがListenしているか確認
sudo ss -tlnp | grep 3000
# または
sudo netstat -tlnp | grep 3000
# 何も表示されない場合、アプリが停止しています — 再起動してください
sudo systemctl restart myapp
# アプリのログを確認
journalctl -u myapp -n 50 --no-pager
そのポートが開いていなければ、Nginxには通信先がありません。設定をいくら変えても解決しません。まずアプリを修正しましょう。
ステップ2 — Nginxが正しい場所を指しているか確認する
サイトの設定ファイルを開きます:
sudo nano /etc/nginx/sites-available/mysite
proxy_passの行を注意深く確認してください:
location / {
proxy_pass http://127.0.0.1:3000; # アプリの実際のポートと一致させること
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
このエラーを確実に引き起こす4つのよくあるミス:
- ポートの誤り — アプリは
3000で動いているのに設定が8080になっている - IPv6の名前解決が予期しない動作をする場合に
127.0.0.1の代わりにlocalhostを使っている - 末尾スラッシュの不一致 —
proxy_pass http://127.0.0.1:3000/とスラッシュなしではパスの書き換え方が変わる - 存在しない、またはパーミッションが間違っているUnixソケットを指定している
ステップ3 — アップストリームのタイムアウト設定を確認する
重いDBクエリ、コールドスタート、または遅い外部API呼び出しによって、バックエンドがNginxのデフォルト60秒タイムアウトを超えることがあります。レスポンスが返ってくる前にプロキシが諦めて502を返します。
location / {
proxy_pass http://127.0.0.1:3000;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
これらの値を増やすことで時間を稼げますが、あくまで応急処置です。常に90秒以上かかるバックエンドは、根本的なパフォーマンス問題として対処する価値があります。
ステップ4 — NginxのエラーログをREADする
まずエラーログを確認しましょう — 何が壊れているかを正確に教えてくれることが多いです:
sudo tail -f /var/log/nginx/error.log
以下のようなメッセージが表示されます:
[error] 12345#0: *1 connect() failed (111: Connection refused) while connecting to upstream,
client: 1.2.3.4, server: example.com, request: "GET / HTTP/1.1",
upstream: "http://127.0.0.1:3000/", host: "example.com"
Connection refused = そのポートでバックエンドが起動していない。
upstream timed out = バックエンドは起動しているが遅すぎるかハングしている。
ステップ5 — Docker / コンテナ環境の場合
DockerでアプリをホストのNginx(または別コンテナ)と組み合わせて動かしている場合、127.0.0.1では動きません — コンテナ内ではそのアドレスはホストマシンではなくコンテナ自身を指します。
# オプション1: ホストゲートウェイIPを使う(Linux上のDocker Desktop)
proxy_pass http://172.17.0.1:3000;
# オプション2: 同じネットワーク内にある場合はDockerの内部DNSを使う
proxy_pass http://app-container-name:3000;
# ゲートウェイIPを確認する
docker network inspect bridge | grep Gateway
docker-compose.ymlでは、両サービスが同じネットワークを共有している必要があります:
services:
nginx:
networks:
- webnet
app:
networks:
- webnet
networks:
webnet:
ステップ6 — TCPポートの代わりにUnixソケットを使う場合
GunicornやuWSGIはTCPポートではなくUnixソケットを使うことが多いです。問題が起きるパターンは2つです:ソケットファイルが存在しない、またはNginxがそれを読み取れない場合です。
# ソケットが存在するか確認
ls -la /run/myapp.sock
# ソケット用のNginx設定
location / {
proxy_pass http://unix:/run/myapp.sock;
}
# Nginxユーザー(www-data)が読み取れない場合はパーミッションを修正
sudo chown www-data:www-data /run/myapp.sock
修正の確認
この順番でテストしてください — 各ステップで異なるレイヤーを確認できます:
# まずNginxの設定構文をテスト
sudo nginx -t
# ダウンタイムなしでリロード
sudo systemctl reload nginx
# バックエンドに直接アクセスできるか確認(Nginxをバイパス)
curl -v http://127.0.0.1:3000/
# 次にNginx経由でテスト
curl -v http://yourdomain.com/
# アクセスログをリアルタイムで確認
sudo tail -f /var/log/nginx/access.log
直接curlで200 OKが返り、Nginx経由でも同様であれば、すべて正しく接続されていることが確認できます。
補足Tips
断続的な502エラー — 負荷時のみ発生し、常に起きるわけではない場合 — は通常アップストリームプールの枯渇を意味します。インスタンスを追加すれば、Nginxがラウンドロビンで振り分けます:
upstream backend {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
keepalive 32;
}
ついでに、ユーザーが生のブラウザ502の代わりに意味のある画面を見られるよう、カスタムエラーページも追加しましょう:
error_page 502 /502.html;
location = /502.html {
root /var/www/html;
internal;
}
プロキシとアップストリーム間のルーティング問題をデバッグする際 — 特にマルチネットワークのDocker環境では — ToolCraftのサブネット計算ツールを使えば、CIDRレンジをすばやく確認し、ホスト同士が同じネットワークセグメントにいるかどうかを検証できます。「なぜ通信できないのか」という問題を素早く発見できます。

