502 和 504 的报错页面长得差不多,但原因几乎相反:502 Bad Gateway 是「上游给了无效响应」,504 Gateway Timeout 是「等上游等到超时」。先分清这两者,能省一半排查时间。
| 错误码 | 含义 | 直接原因 |
|---|---|---|
| 502 | 上游返回了无法解析的响应,或连接被断开 | 上游进程崩了 / 假死 / 端口没监听 / 连接被重置 |
| 504 | 上游在规定时间内没响应 | 上游处理慢(慢查询、死锁、外部 API 慢) |
| 503 | Nginx 自身拒绝服务 | 超出 worker_connections、维护中 |
| 499 | 客户端主动断开 | 用户等不及关页面了(不是 Nginx 的问题) |
第一步:先确认是哪一层挂了
别一上来就改 Nginx 配置。先确认上游服务本身是否活着。
# 1. 从宿主机直连上游(绕过 nginx)
curl -v http://127.0.0.1:9000/ # PHP-FPM
curl -v http://127.0.0.1:8080/health # 后端应用
1. 2. 上游端口是否在监听
ss -lntp | grep -E ':9000|:8080'
1. 3. 若上游是容器,从 nginx 容器内部测(网络隔离常被忽略)
docker exec <nginx容器> curl -sv http://<上游容器名>:8080/health
1. 4. 看上游进程数
ps aux | grep -c "[p]hp-fpm"第 3 步最容易发现问题:宿主机 curl 通了,但从 nginx 容器 curl 不通——那是容器网络问题(不在同一网络、IP 变了、DNS 解析失败),跟 Nginx 配置无关。
第二步:读 error.log 的关键字段
error.log 每条记录都带 upstream 状态,这是定位的核心。
tail -50 /var/log/nginx/error.log
1. 典型 502
1. upstream prematurely closed connection while reading response header
1. from upstream, client: 1.2.3.4, request: "GET / HTTP/1.1",
1. upstream: "fastcgi://172.26.0.13:9000"
1. 典型 504
1. upstream timed out (110: Connection timed out) while reading
1. response header from upstream, upstream: "http://172.26.0.11:8080"| 日志关键词 | 指向哪里 | 下一步 |
|---|---|---|
| prematurely closed connection | 上游主动断连(崩溃或自杀) | 查上游日志与进程状态 |
| connect() failed (111: Connection refused) | 端口没监听 | 上游没起来或监听地址错 |
| no live upstreams | 全部上游被标记不可用 | 健康检查把上游全判死了 |
| timed out (110) | 上游处理超过阈值 | 查上游是否在跑慢查询 |
| upstream sent too big header | 上游响应头过大 | 调 proxy_buffer_size |
| recv() failed (104: Connection reset) | 连接被 RST | 上游被杀或防火墙 |
第三步:504 专项——超时到底卡在哪
504 的本质是「Nginx 等不到」。要继续往下钻:上游自己是不是也在等它的后端?
# PHP-FPM 慢日志
1. php-fpm.d/www.conf:
1. request_slowlog_timeout = 5s
1. slowlog = /var/log/php-fpm/slow.log
tail -20 /var/log/php-fpm/slow.log
1. MySQL 慢查询
mysql -e "SHOW VARIABLES LIKE 'slow_query_log'"
mysql -e "SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 5"
1. 看进程卡在哪个系统调用
strace -p $(pgrep -f "php-fpm: pool www" | head -1) -T 2>&1 | head -20常见的 504 根因链条:
- Nginx 等 PHP-FPM 30 秒 → PHP-FPM 在等 MySQL → MySQL 有锁等待:只调 Nginx 的 timeout 毫无意义,要解决 MySQL 的锁
- PHP-FPM 进程池被占满:所有 worker 都在处理慢请求,新请求排队,表现为「有时 502 有时 504」
- 外部 API 超时:应用调第三方接口没设超时,一路挂到底
- DNS 解析慢:resolver 配置不当,每次请求都卡在解析
第四步:超时参数的优先级(最容易配错)
Nginx 的超时是一串,改错地方等于没改。
| 层 | 参数 | 默认 | 说明 |
|---|---|---|---|
| Nginx → 上游 建连 | proxy_connect_timeout / fastcgi_connect_timeout | 60s | TCP 握手超时,内网应设很小(1-3s) |
| Nginx → 上游 发送 | proxy_send_timeout | 60s | 发请求体的超时 |
| Nginx → 上游 读取 | proxy_read_timeout / fastcgi_read_timeout | 60s | 最常需要调的,等响应的上限 |
| PHP-FPM 自身 | request_terminate_timeout | 0(不限) | FPM 侧执行上限,比 Nginx 小才生效 |
| PHP 脚本 | max_execution_time | 30s | PHP 侧执行上限 |
关键规则:谁小谁生效。Nginx 的 read_timeout 设 300s,但 PHP 的 max_execution_time 是 30s,结果还是 30 秒 504——而且报错来自 FPM 而不是 Nginx,容易看错方向。
# 一次统一检查所有超时
grep -rE "connect_timeout|send_timeout|read_timeout|terminate_timeout" \
/etc/nginx/ /etc/php*/ 2>/dev/null
php -i | grep -E "max_execution_time|max_input_time"第五步:502 专项——PHP-FPM 假死
PHP-FPM 有一种特殊状态:进程都在,但全部卡死,不接新请求。日志看不到崩溃,表现为 502。
# 1. 看 FPM 状态(需先开 status page)
curl http://127.0.0.1/status?full
1. pool: www process manager: dynamic
1. active processes: 5 (max 5) ← 满了
1. listen queue: 20 (max 128) ← 有排队
1. max children reached: 2 ← 达到上限次数
1. 2. 看进程卡在哪个系统调用
for pid in $(pgrep -f "php-fpm: pool www"); do
cat /proc/$pid/wchan; echo
done
1. 3. 用 strace 看具体阻塞点(会短暂暂停进程,谨慎)
strace -p <pid> -T 2>&1 | head -20- listen queue 有值 = 请求在排队,说明 worker 不够或都在处理慢请求
- max children reached 增长 = 池子太小,要调 pm.max_children
- 进程都在但 active=0 = 可能连不上后端(如 MySQL),在等连接超时
- 重启 FPM 只是临时缓解:根因通常是慢查询或外部调用,不解决会复发
第六步:容器环境特有的坑
| 现象 | 原因 | 修复 |
|---|---|---|
| 宿主机 curl 通,nginx 容器不通 | 容器不在同一 network | 都把服务挂到同一 network |
| IP 变了导致 502 | 用默认 bridge,容器重建后 IP 改变 | 用固定 IP 或按容器名解析 |
| 间歇性 502 | 上游容器在重启中 | 配 depends_on: service_healthy |
| 连接被拒且日志有 111 | 上游监听 127.0.0.1 而非 0.0.0.0 | 改监听地址为 0.0.0.0 |
| 413 而非 502 | 请求体超过 client_max_body_size | 调大该值(上传功能常见) |
# 固定网段 + 固定 IP,避免容器重建后 IP 变化
networks:
backend:
ipam:
config:
- subnet: 172.26.0.0/24
services:
nginx:
networks:
backend:
ipv4_address: 172.26.0.10
app:
networks:
backend:
ipv4_address: 172.26.0.13固定 IP 的价值:Nginx 配置里写
fastcgi_pass 172.26.0.13:9000就不会因容器重建而失效。若依赖容器名解析,DNS 缓存与重启时序都会带来间歇性 502。
一张表记住判断顺序
| 现象 | 先查 | 再查 | 最后查 |
|---|---|---|---|
| 502 + premature close | 上游进程存活? | 上游日志有无崩溃 | 上游是否被 OOM 杀 |
| 502 + connection refused | 端口是否监听 | 监听地址是否 0.0.0.0 | 防火墙/网络隔离 |
| 502 + no live upstreams | 健康检查配置 | 上游是否真不健康 | 检查间隔是否过短 |
| 504 | 超时参数谁最小 | 上游慢日志 | 上游在等什么(DB/外部 API) |
| 502 与 504 交替 | FPM worker 是否满 | listen queue 是否堆积 | 调 pm.max_children |
总结
- 先分清 502 和 504:一个是响应无效,一个是等超时,排查方向完全不同
- 从 nginx 容器内部测上游:宿主机通不代表容器通,这是容器环境最常见误判
- 504 要往下钻两层:Nginx 等 FPM,FPM 可能在等 MySQL——只调 Nginx 超时无效
- 超时参数取最小值生效:Nginx 300s 但 PHP 30s,结果就是 30s 报错
- 502 交替 504 = worker 池满,根因通常是慢请求,不是 Nginx 配置