线上接口偶发或直接大面积返回 Nginx 504 Gateway Timeout,和 502 一样让人头大,但根因恰恰相反:Nginx 504 表示“网关已经连上上游,但上游在限定时间内没能返回响应”。本文从 error.log 出发,逐层剖析 Nginx 504 的五大根因——慢查询、超时配置、反代链过长、后端阻塞、大响应——并给出分级超时与异步化的根治方案。
一、504 与 502 的本质区别
502 是“连不上 / 被拒”,504 是“连上了但太慢”。二者排查路径完全不同(对比见《Nginx 502 排查实战》)。Nginx 作为反向代理(见《Nginx 反向代理完整配置》),对上游的每一次转发都有超时上限,超过就返回 504。
二、第一步:从 error.log 确认是超时
先读日志,关键字直接区分 502 与 504:
tail -f /var/log/nginx/error.log
# Nginx 504 的典型关键字
# upstream timed out (110: Connection timed out) -> 读超时
# upstream timed out ... while reading response header -> 后端处理太久
# 注意:它不是 502 的 "Connection refused",而是 "timed out"
三、根因一:后端真的慢(慢 SQL / 慢接口)
最常见。用 curl 复现并计时,确认是后端耗时:
curl -o /dev/null -w "time_total=%{time_total}s\n" https://api.example.com/slow
# 若数据库查询慢,从索引与 SQL 入手
# PostgreSQL:见《PostgreSQL 慢查询优化》
# MySQL:见《MySQL 索引底层原理》
# Java 服务 GC 停顿:见《Java 内存泄漏排查复盘》
四、根因二:超时配置过短
默认 proxy_read_timeout 为 60s,但链路深处常常不够。按业务 P99 放宽:
location /api/ {
proxy_pass http://backend;
proxy_connect_timeout 5s;
proxy_read_timeout 120s; # 按真实 P99 调整,而非无脑拉满
proxy_send_timeout 120s;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
PHP-FPM 同步改 fastcgi_read_timeout;Spring Boot / 网关侧也要对应用户的读取超时,避免“上游已断、下游还在等”的错配。
五、根因三:反代链过长,超时叠加
当链路是 Nginx → Nginx → 后端 时,外层超时必须大于内层之和,否则内层还在算、外层已断:
# 边缘 Nginx(面向用户)
proxy_read_timeout 120s;
# 内层 Nginx(转发到后端)
proxy_read_timeout 90s;
# 后端处理应 <= 60s,否则继续下钻后端
六、根因四:后端被阻塞(锁 / 同步 / 事件循环)
连接池满、慢锁、同步调用第三方都会让线程卡住,表现为请求排队超时。本质与 MySQL 连接数满同类(见《MySQL 连接数满排查实战》);文件描述符耗尽也会表现为超时(见《文件描述符耗尽排查》):
ss -s # 看总 TCP 连接与端口分配,是否堆积
jstack <java_pid> | grep -A20 "BLOCKED" # Java 看线程栈锁竞争
netstat -an | grep :8080 | wc -l # 活跃连接是否逼近上限
七、根因五:大文件 / 大响应传输慢
导出大报表、大附件会拖慢首字节。优先用流式响应、分页、异步生成后回调,或走对象存储直链;Docker 部署形态见《Docker 编排实战》。
八、排查清单(速查表)
| error.log 关键字 | 最可能根因 | 优先动作 |
|---|---|---|
| upstream timed out | 读超时 | 调 proxy_read_timeout、查慢接口 |
| timed out (while reading response header) | 后端处理太久 | 查慢 SQL / GC / 锁 |
| connect timeout | 连不上后端 | 查网络 / 安全组 / 端口 |
| 494 / 循环超时 | 反代链超时叠加 | 对齐各层超时 |
九、治本:分级超时 + 熔断 + 异步化 + 监控
临时放宽不算完。给 upstream 配熔断、把长任务异步化(消息队列)、在《GitHub Actions CI》里加超时断言,并用《Uptime Kuma 自建监控》盯 P95 响应。把排障脚本沉淀成《VS Code 代码片段》,下次定位更快:
upstream backend {
server 10.0.0.11:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.12:8080 max_fails=3 fail_timeout=30s;
}
location /api/ {
proxy_pass http://backend;
proxy_read_timeout 120s;
proxy_next_upstream error timeout http_504;
}
# 异步探活:长任务改为先返回任务号,轮询结果
curl -X POST https://api.example.com/jobs -d '{"task":"export"}'
十、小结
Nginx 504 的核心是“超时”,先确认是超时而非崩溃,再按“慢查询 → 超时配置 → 反代链 → 后端阻塞 → 大响应”逐层排除。把超时做成分级、把长任务做成异步,504 就能从频发变偶发。




