Nginx 502 Bad Gateway 排错实战

一、502 与 504:先分清敌人

当你在浏览器里看到「502 Bad Gateway」,往往意味着 Nginx 作为反向代理,没能从上游(upstream)应用拿到合法响应。本文以生产排错视角,带你从 upstream、FastCGI 到代理缓冲区,一步步定位 502 的真正根因。502 是运维中最常见的“网关类”错误之一,但只要方法对,绝大多数都能在十分钟内闭环。

很多人把 502 和 504 混为一谈,其实二者方向不同。502 Bad Gateway:Nginx 联系上了上游,但上游返回了非法响应(连接被拒、进程崩溃、返回空响应)。504 Gateway Timeout:Nginx 联系上了上游,但上游在超时时间内没有返回。简单记:502 是“上游活着但答非所问或干脆挂了”,504 是“上游太慢答不上来”。下面聚焦更棘手的 502,因为它的根因往往藏在网络与进程的交界处。

二、根因一:upstream 进程没起来或端口错配

最最常见的 502,是反向代理指向的端口根本没有人监听。先绕过 Nginx,直接打上游,能立刻区分是 Nginx 配置问题还是应用本身的问题,这一步可以省掉大量无谓的猜测。

2.1 先确认端口是否有人监听

# 查看 8080 端口是否被监听
ss -lntp | grep 8080
# 或用 curl 直接打 upstream,绕开 Nginx
curl -v http://127.0.0.1:8080/health

如果 curl 直接返回 Connection refused,说明应用没启动,或监听了别的地址/端口(比如只监听了 IPv6 的 ::1,而 Nginx 用 IPv4 的 127.0.0.1 去连)。先拉起应用并确认监听地址,再回到 Nginx 验证。

2.2 核对 upstream 地址与端口

upstream app {
    server 127.0.0.1:8080;
}
server {
    location / {
        proxy_pass http://app;
    }
}

一个经典坑:应用监听的是容器内的 127.0.0.1:8080,但 Nginx 在宿主机,跨网络空间访问不到;或者 Docker Compose 里应该用服务名 http://app:8080 而非 127.0.0.1。这类“端口对得上但连不上”的 502,往往藏在容器网络隔离里,用 docker exec 进 Nginx 容器内部 curl 上游是最快验证手段。

2.3 用 upstream 块还是直接 proxy_pass

当只有一个后端时,直接 proxy_pass http://127.0.0.1:8080 与定义 upstream 块等价;但 upstream 块支持负载均衡与健康检查,多节点场景下更易排查“某个节点下线导致 no live upstreams”的问题。无论哪种写法,目标地址必须与实际监听完全一致。

2.4 双栈环境的隐形坑

还有一个容易踩的坑是双栈环境:应用只监听 IPv6 的 ::1,而 Nginx 用 127.0.0.1(IPv4)去连,同样会得到 Connection refused。用 ss -lntp 时务必看清 Local Address 是 0.0.0.0(IPv4 全网卡)还是 :::(IPv6 全网卡),二者并不互通,错配就是 502。

三、根因二:FastCGI / PHP-FPM 场景

对 PHP 站点,502 常常来自 PHP-FPM 挂掉或请求超时。先看 Nginx 的 fastcgi 配置是否指向了正确的 FPM 端口。

location ~ \.php$ {
    fastcgi_pass   127.0.0.1:9000;
    fastcgi_index  index.php;
    include        fastcgi_params;
}

对应的 PHP-FPM 池配置也别忽视,进程数触顶或脚本死循环都会让人拿到 502:

; www.conf
pm = dynamic
pm.max_children = 50
request_terminate_timeout = 30s

如果 request_terminate_timeout 设成 0(永不超时)但脚本死循环,进程会被占满,新请求只能得到 502。把超时设成合理值,并观察 php-fpm 进程数是否触顶,必要时结合 pm.max_children 扩容。

四、代理超时与 keepalive 连接复用

另一个容易被忽视的 502 来源是代理超时与连接复用配置不当。默认 proxy_read_timeout 是 60 秒,若上游处理偶尔超过该值,Nginx 会主动断开并向上游返回 502/504。

proxy_connect_timeout 5s;
proxy_read_timeout    60s;
proxy_send_timeout    60s;
upstream app {
    server 127.0.0.1:8080;
    keepalive 32;
}
location / {
    proxy_pass http://app;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
}

keepalive 复用后端连接能显著降低新建连接的开销,但要配合 proxy_http_version 1.1 并清空 Connection 头,否则在高并发下连接管理混乱也会引发间歇性 502。

超时参数 默认值 调大场景
proxy_connect_timeout 60s 上游启动慢、跨机房
proxy_read_timeout 60s 报表/导出类慢请求
proxy_send_timeout 60s 大请求体上传

五、看 error.log:一条日志锁定方向

不要盲猜,直接翻 Nginx 错误日志,502 的“元凶”通常明明白白写在里面:

grep "502" /var/log/nginx/error.log
# 常见三类
# connect() failed (111: Connection refused)
# upstream prematurely closed connection
# upstream timed out
error.log 信息 真实含义 排查方向
connect() failed (111: Connection refused) 上游端口没监听 检查应用进程 / 端口
upstream prematurely closed connection 上游进程崩溃或主动断连 查应用日志、OOM
Permission denied (13) SELinux / 权限拦截 关 SELinux 或设布尔值
no live upstreams upstream 全部下线 健康检查 / 节点状态

六、代理缓冲区与请求头过大

某些请求带了大 Cookie 或长请求头,超过 Nginx 默认缓冲区,也会触发 502。适当调大缓冲区,并确保正确透传 Host 与真实 IP。

proxy_buffer_size   16k;
proxy_buffers       4 32k;
proxy_busy_buffers_size 64k;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;

七、SELinux、防火墙与 Docker 网络

如果 error.log 出现 Permission denied (13),大概率是 SELinux 阻止了 Nginx 发起网络连接到上游:

# 临时放行
setsebool -P httpd_can_network_connect 1
# Docker 场景确认网络
docker network ls
docker exec nginx curl -v http://app:8080/health

在 Docker Compose 中,Nginx 与 upstream 必须处于同一自定义网络,且用服务名互访,否则就会得到“连不上”的 502。

八、监控与告警:别等用户报障

502 最好的处理方式是“不发生”或“秒级发现”。在 Nginx 侧可借助 Prometheus 的 blackbox_exporter 对 upstream 做 HTTP 探活,一旦连续探测失败立即告警,而不是等用户投诉。结合站内已有的 Prometheus + Grafana 监控体系,可以把 502 率作为核心 SLO 指标:定义「5xx 比例 = 5xx 状态码数 / 总请求数」,当 502 占比超过阈值(如 0.5%)自动触发告警。这样即使深夜上线引入监听地址错误,也能在用户大规模受影响前被拦截,把故障窗口从小时级压缩到分钟级。

九、真实案例:一次大促前的 502 雪崩

曾有一次上线,压测开始后接口全线 502。error.log 全是 connect() failed (111: Connection refused),但应用进程明明在。最后定位是:新版本把监听地址从 0.0.0.0:8080 改成了 127.0.0.1:8080,而 Nginx 跑在 sidecar 容器里,127.0.0.1 指向的是容器自己,自然连不上。改成 0.0.0.0 后 502 立刻消失。这类“配置一个小改动引发全线 502”的案例,靠的正是“先 curl 直打上游、再看 error.log”的两步法,比盲目重启服务高效得多。

十、生产排错清单

步骤 动作 目的
1 curl 直打 upstream 确认应用本身是否正常
2 看 error.log 锁定连接拒绝 / 超时 / 权限
3 核对 upstream 地址端口 排除错配与容器网络
4 查 PHP-FPM / 应用进程数 排除进程耗尽
5 检查超时与 keepalive 排除慢请求与连接复用
6 检查 SELinux / 防火墙 排除被拦截
7 加监控告警 把故障窗口压到分钟级

关于反向代理的基础配置,可参考站内的 Nginx 反向代理实战;若还伴随限流类异常,可对照 Nginx 限流配置。把容器化部署跑顺,可看 Docker 实战。

结语:502 不可怕,可怕的是没有日志就乱改配置。抓住「upstream 是否活着、能否正常响应」这条主线,配合 error.log 的精确报错与一套趁手的监控告警,绝大多数 502 都能在十分钟内定位、在影响扩大前被拦下。

上一篇 微前端架构实战:用 Module Federation 拆分巨石应用
下一篇 远程工作实战:程序员高效居家办公与协作指南
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]