本期摘要
502 Bad Gateway 是 Nginx 最让人头疼的错误码之一。它不像 404 那样明确(文件不存在),而是表示"上游服务器返回了无效响应"。本文从一次真实故障切入,按"客户端 → Nginx → 应用 → 数据库"的链路分层排查,涵盖 upstream 超时、连接数耗尽、缓冲区不足、PHP‑FPM 崩溃、内核 backlog 溢出等常见原因。读完你就能在收到 502 告警时,快速定位是 Nginx 自身问题、后端应用问题,还是系统层面问题。
一次让人怀疑人生的故障
某个促销日,业务高峰,大量用户反馈页面打不开。Nginx 日志里全是:
text
[error] 12345#0: *987654 connect() failed (110: Connection timed out) while connecting to upstream
502 扑面而来。运维同事第一反应:"后端服务挂了?"但检查应用进程,一切正常;检查数据库,也无异常。重启 Nginx 后短暂恢复,几分钟后又出现 502。
折腾了两个小时,发现问题出在 Nginx 与 PHP‑FPM 之间的 keepalive 配置不当,加上 upstream 响应过慢,导致连接池被耗尽。
502 的本质
Nginx 作为反向代理,接收到客户端请求后,需要向 upstream(后端应用服务器)发起新的请求。如果 Nginx 无法得到有效的 HTTP 响应,就会返回 502。
常见的上游类型:
-
HTTP 后端(Tomcat、Gunicorn、PHP‑FPM、Java 应用)
-
FastCGI(PHP‑FPM)
-
uWSGI(Python)
-
代理服务器本身
排查四步法
第一步:查看 Nginx 错误日志
bash
tail -f /var/log/nginx/error.log
错误日志会给出具体原因,常见错误包括:
| 日志关键字 | 含义 | 排查方向 |
|---|---|---|
connect() failed (110: Connection timed out) |
连接上游超时 | upstream 服务不可达或响应慢 |
connect() failed (111: Connection refused) |
上游端口没开 | upstream 服务未启动或挂掉 |
upstream timed out (110: Connection timed out) |
读取响应头超时 | upstream 处理太慢 |
no live upstreams |
所有上游都挂了 | 检查 upstream 健康状态 |
client intended to send too large body |
请求体过大 | 调大 client_max_body_size |
第二步:检查 upstream 服务状态
如果是 HTTP 代理,直接 curl 测试:
bash
curl -v http://127.0.0.1:8080/health # 假设 upstream 在 8080 端口
如果超时或拒绝连接,问题在后端应用。
如果是 PHP‑FPM:
bash
systemctl status php7.4-fpm
netstat -an | grep 9000 # 检查端口监听
第三步:检查 Nginx 与 upstream 之间的连接
连接数是否耗尽:
bash
ss -an | grep 8080 | wc -l # 看连接数
netstat -an | grep 8080 | grep TIME_WAIT | wc -l
如果 TIME_WAIT 堆积过多,调整内核参数或 Nginx keepalive。
upstream 响应时间:
在 Nginx 日志中记录请求时间:
bash
log_format timed '$remote_addr - $request_time - $upstream_response_time - $request';
access_log /var/log/nginx/access.log timed;
-
$request_time:Nginx 处理总时间 -
$upstream_response_time:上游服务器响应时间
如果 $upstream_response_time 很大,说明后端慢;如果不大但 $request_time 大,可能是 Nginx 发送响应给客户端慢(网络问题)。
第四步:系统层面检查
net.core.somaxconn(监听队列长度)
bash
sysctl net.core.somaxconn # 默认 128,高并发时容易溢出
检查 Nginx backlog 配置
bash
listen 80 backlog=1024; # 增加队列容量
文件句柄限制:
bash
ulimit -n # 查看当前进程可打开文件数
# 修改 /etc/security/limits.conf
* soft nofile 65535
* hard nofile 65535
常见场景及解决方案
| 场景 | 日志特征 | 解决方案 |
|---|---|---|
| PHP‑FPM 响应慢 | upstream timed out, 但 curl 能通 |
调大 fastcgi_read_timeout,优化 PHP 代码 |
| 后端服务连接池满 | connect() failed (110: Connection timed out) |
增加后端服务连接池,或调大 Nginx proxy_connect_timeout |
| TIME_WAIT 爆炸 | ss 看到大量 TIME_WAIT |
开启 proxy_http_version 1.1 和 proxy_set_header Connection "",启用 keepalive |
| 请求体过大 | client intended to send too large body |
增加 client_max_body_size |
| backlog 溢出 | listen queue overflow 系统日志 |
增大 net.core.somaxconn 和 Nginx backlog |
| 僵尸 upstream 被标记为 down | no live upstreams |
检查健康检查配置,必要时用 max_fails=0 禁止标记为 down |
永久防范方案
1. 合理设置超时参数
nginx
http {
proxy_connect_timeout 5s; # 连接上游超时
proxy_send_timeout 10s; # 发送数据超时
proxy_read_timeout 10s; # 接收响应超时
fastcgi_read_timeout 60s; # FastCGI 专用
}
2. 启用 upstream keepalive
nginx
upstream backend {
server 127.0.0.1:8080;
keepalive 32; # 保持空闲连接数
}
server {
location / {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://backend;
}
}
3. 使用健康检查
商业版 Nginx 支持主动健康检查。开源版可通过 max_fails 和 fail_timeout 控制:
nginx
upstream backend {
server 10.0.0.1:8080 max_fails=3 fail_timeout=30s;
}
4. 监控与告警
-
监控 Nginx error.log 中 502 的数量
-
监控 upstream_response_time 的 P99 值
-
监控 Nginx 连接数、TIME_WAIT 数量
下期预告
《100个"反常识"经验16:Docker容器退出,日志里什么都没有》