nginx「502 Bad Gateway」の原因と直し方
502 Bad Gateway は、error.log の理由を読み、転送先の起動と proxy_pass の宛先を直せば消える。
nginx が転送先から正しい応答を受け取れないと返すエラーで、多いのは connect() failed (111: Connection refused) である。
公開:
要約
nginx の 502 Bad Gateway は、転送先のアプリ(upstream)から正しい応答を受け取れないときに返る。
画面には理由が出ないので、error.log の行を読んで原因を分ける。
nginx の公式イメージでは error.log が標準エラーへ向いているので、docker logs で読める。
直接入れた場合は、多くの環境で /var/log/nginx/error.log に出る。
docker logs my-nginx 2>&1 | grep -E '\[(error|crit)\]'
tail -n 20 /var/log/nginx/error.log| ログの文言 | 起きていること |
|---|---|
connect() failed (111: Connection refused) while connecting to upstream | その宛先で待ち受けているものが無い |
connect() to unix:/run/app.sock failed (2: No such file or directory) | ソケットファイルが無い |
upstream sent too big header while reading response header from upstream | 応答ヘッダがバッファに収まらない |
SSL_do_handshake() failed (SSL: error:0A00010B:SSL routines::wrong version number) | 平文の転送先へ TLS で接続している |
解決策
1. 転送先を起動して宛先を合わせる
ログの upstream: には、nginx が実際に接続した宛先が出ている。
そのアドレスへ nginx と同じ場所から接続して確かめる。
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3000/Failed to connect が出るなら、そのアドレスとポートで待ち受けているものが無い。
アプリを起動するか、proxy_pass を実際の待ち受けに合わせる。
2. コンテナでは localhost へ転送しない
同じネットワークのコンテナへは、サービス名かコンテナ名で転送する。
アプリ側の待ち受けは 0.0.0.0 にする。
location / {
proxy_pass http://app:3000;
}アプリがホストで動いているなら host.docker.internal へ転送する。
Linux の Docker Engine では、docker run に次のオプションを足すとこの名前が使える。
docker run --add-host=host.docker.internal:host-gateway nginx3. 作り直したコンテナの名前を引き直す
reload すれば名前を引き直す。
nginx -s reload毎回の reload を避けるなら、宛先を変数に入れて resolver を指定すると、リクエストのたびに名前が解決される。127.0.0.11 は、Docker のユーザー定義ネットワークの DNS である。
server {
resolver 127.0.0.11 valid=10s;
location / {
set $upstream http://app:3000;
proxy_pass $upstream;
}
}4. 応答ヘッダのバッファを増やす
upstream sent too big header が出ているときは、location にバッファの設定を足す。
location / {
proxy_pass http://app:3000;
proxy_buffer_size 16k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
}5. proxy_pass のスキームを合わせる
転送先が平文の HTTP なら、proxy_pass は http:// で書く。
見分け方は OpenSSL の記事にある。
よくある原因
- 転送先のアプリが止まっているか、
proxy_passのポート番号やソケットのパスが実際の待ち受けと違う。
ログには111: Connection refusedと出る。 - コンテナの nginx から
localhostへ転送している。
コンテナの中のlocalhostは nginx のコンテナ自身を指す。
アプリが127.0.0.1だけで待ち受けている場合も、別のコンテナからの接続は同じ111で拒否される。 - 転送先のコンテナを作り直して IP アドレスが変わった。
proxy_passに直接書いたホスト名は設定の読み込み時に解決されるので、nginx は古いアドレスへ接続し続ける。 - アプリの応答ヘッダが大きく、
proxy_buffer_sizeに収まらない。
既定値はメモリの 1 ページ分(4k か 8k)で、Set-Cookieなどのヘッダが増えると超える。 proxy_passをhttps://にしているが、転送先は平文の HTTP で待ち受けている。