Docker 里 Nginx 反代后端应用时,502 Bad Gateway 是最常见的报错之一。它只说明一件事:Nginx 连不上上游(upstream)。排查思路很固定:先看上游活没活,再看网络和配置。很多新手会先去改 Nginx 配置,其实第一步应该是确认应用容器真的在监听正确的地址和端口。
下面这套流程按「从外到内」的顺序排查:先确认容器状态,再确认网络连通性,最后看应用监听地址和 Nginx 配置。按部就班列下来,基本不会漏。
502 常见根因速查
| 根因 | 典型日志 | 占比 |
|---|---|---|
| 上游容器未运行 | connect() failed (111: Connection refused) | 35% |
| 应用绑定 127.0.0.1 | upstream prematurely closed connection | 25% |
| 不在同一 Docker 网络 | no resolver defined to resolve app | 20% |
| 上游端口/路径写错 | upstream port mismatch | 15% |
| 上游 OOM / 崩溃 | exit code 137 / OOMKilled | 5% |
四步定位
1. 确认上游容器在跑
docker ps --filter name=app
# 如果状态是 Exited,看日志
docker logs app --tail 50
2. 从 Nginx 容器内部测连通性
docker exec nginx getent hosts app
docker exec nginx curl -v http://app:3000/health
如果 getent hosts 解析不到,说明两个容器不在同一网络,或者服务名写错。
3. 检查应用监听地址
很多新手写 app.run(host='127.0.0.1', port=3000),这在容器里只能被自己访问。Nginx 从外部连过来必须改成:
app.run(host='0.0.0.0', port=3000)
4. 检查 Nginx 配置语法
docker exec nginx nginx -t
docker exec nginx nginx -T | grep -A5 proxy_pass
一个能跑的 docker-compose 模板
services:
app:
build: ./app
expose:
- "3000"
environment:
- HOST=0.0.0.0
networks:
- backend
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- app
networks:
- backend
networks:
backend:
对应 Nginx 配置:
upstream app {
server app:3000;
}
server {
listen 80;
location / {
proxy_pass http://app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
宿主机防火墙与安全组
如果 Nginx 和 app 在同一 Docker 网络里能互相访问,但浏览器还是 502,要检查外层:
- Linux 主机的 ufw / firewalld 是否放行了 80/443
- 云服务器安全组是否允许入站 HTTP/HTTPS
- SELinux 是否阻止了容器间通信(可临时设为 Permissive 测试)
# 快速检查端口是否监听在宿主机上
ss -tlnp | grep :80
# 检查 ufw 状态
sudo ufw status
# 检查 firewalld
sudo firewall-cmd --list-all
docker-compose 里用 ports: 把容器端口映射到宿主机后,外部流量先到宿主机防火墙,再到 Docker 的 iptables 规则,最后才进容器。
Nginx 超时参数调优
如果上游响应慢,Nginx 默认 60 秒超时可能不够,会报 504 而不是 502。但在调试阶段,先把超时放大可以避免误判:
location / {
proxy_pass http://app;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 120s;
proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;
}
生产环境别盲目放大,应该根据应用真实 P99 latency 来设。调试完后,记得把 proxy_read_timeout 调回合理值。
网络排查命令速查
这几个命令组合起来用,能快速定位网络层问题:
# 查看容器所在网络
docker network ls
docker inspect backend --format '{{range .Containers}}{{.Name}} {{.IPv4Address}}{{end}}'
# 从 Nginx 容器解析服务名
docker exec nginx nslookup app
# 查看 Nginx 实际生效配置
docker exec nginx nginx -T
# 实时跟踪 error log
docker exec nginx tail -f /var/log/nginx/error.log
如果 nslookup app 返回 NXDOMAIN,说明服务名没加入同一网络,或者 compose 里的网络名称不一致。
踩坑实录
有次我配了一个 Flask 容器,docker ps 显示 Up,Nginx 还是 502。进 Nginx 容器 curl http://app:5000 不通,但 docker logs app 没报错。后来用 docker exec app ss -tlnp 一看,监听的是 127.0.0.1:5000。把启动命令从 flask run 改成 flask run --host=0.0.0.0 后,502 立刻消失。这个坑在没有明确报错时最难查,必须主动看监听地址。
常见问题
问:502 和 504 怎么区分?
答:502 是 Nginx 连不上上游或上游返回无效响应;504 是上游连上了但超时没回数据。看 Nginx error_log 里的错误码最准。
问:为什么不用容器 IP 而要用服务名?
答:容器重启后 IP 会变,Docker 内置 DNS 会把服务名解析到当前容器 IP,用服务名更稳。
问:健康检查应该怎么配?
答:给应用加一个轻量的 /health 端点,只返回状态不要查数据库,避免健康检查本身把服务压垮。
关于 Docker 和 Nginx 的更多排错案例,可以到 fenij.com 留言交流。