页面转圈十几秒,最后甩出「504 Gateway Time-out」;打开访问日志,又发现一排没人认识的 499。两个都像是"超时",但锅不在同一个人身上。这篇把 504 和 499 拆开,用四步定位到真正的慢点。
一、先分清 504 和 499 分别在说谁
很多人把这两个码混着看,结果越查越乱。区别其实很干脆:
- 504 Gateway Time-out :Nginx 把请求转给后端后,等不到响应就放弃了。锅在后端或链路慢------后端还在处理,只是超过了 Nginx 的等待上限。
- 499 :Nginx 自己发明的非标准码(Nginx 日志里才有,HTTP 标准里没有),含义是 client closed request ------请求还没返回,客户端先断开连接了。锅常常在"客户端等不及",而它等不及的原因,往往又是后端真的慢。
所以线上常见的一幕是:后端慢 → 客户端(或上游负载均衡、手机 App)先超时断开 → Nginx 记一条 499;如果客户端够有耐心等到 Nginx 自己超时,那就是 504。
结论:504 和 499 经常是同一个病根的两个表现,别当成两个独立故障查。
二、第 1 步:让日志说出"到底谁慢"
默认的 access log 看不出时间花在哪,先把三个时间字段加进 log_format:
nginx
log_format timed '$remote_addr $request '
'rt=$request_time '
'urt=$upstream_response_time '
'us=$upstream_status '
'ua=$upstream_addr '
'st=$status';
access_log /var/log/nginx/access.log timed;
四个字段是排查的核心:
$request_time:Nginx 从收到第一个字节到发完响应的总耗时;$upstream_response_time:Nginx 发给后端、到收完后端响应的时间;$upstream_status:后端返回的状态码(可能多个,用逗号分隔,对应多次重试);$upstream_addr:真正处理这次请求的后端地址(多实例时能定位到具体那台)。
reload 之后,用两行 awk 就能把慢请求捞出来:
bash
# 按 upstream 耗时排序,看最慢的 20 条
awk -F'urt=' '{split($2,a," "); print a[1], $0}' access.log | sort -rn | head -20
# 统计 504/499 出现的频次与对应后端
grep -E ' (504|499) ' access.log | awk '{print $NF, $(NF-3)}' | sort | uniq -c | sort -rn
判读原则很直接:
rt很大、urt也很大 → 慢在后端,继续查后端;rt很大但urt很小甚至为-→ 慢在 Nginx 或客户端这一侧(大响应体、慢客户端、带宽打满)。
三、第 2 步:确认是"后端超时"还是"连接就不通"
看 error.log 的关键字,能一次分辨两种性质完全不同的 504:
bash
grep -i 'upstream timed out' /var/log/nginx/error.log | tail -20
upstream timed out (110: Connection timed out) while reading response header from upstream
→ 连上了后端,但后端迟迟不吐响应头。这是后端处理慢,最常见。upstream timed out (110: ...) while connecting to upstream
→ 连后端都连不上。这是网络或后端端口问题,不是慢,是通不通。no live upstreams/connect() failed (111: Connection refused)
→ 后端进程挂了或连接池被打满。
紧接着核对生效的超时配置:
bash
nginx -T | grep -E 'proxy_(connect|send|read)_timeout|keepalive'
三个超时的分工要分清:
| 指令 | 管的是什么 |
|---|---|
proxy_connect_timeout |
与后端建立连接的等待上限 |
proxy_send_timeout |
向后端发送请求的两次写操作间隔上限 |
proxy_read_timeout |
等待后端返回响应的两次读操作间隔上限(504 最常撞的就是它) |
四、第 3 步:直连后端,把 Nginx 摘出去
怀疑是 Nginx 就先绕开它。在 Nginx 机器上直连后端端口,用 curl 把耗时分段打出来:
bash
curl -o /dev/null -s -w 'dns=%{time_namelookup} conn=%{time_connect} ttfb=%{time_starttransfer} total=%{time_total}\n' \
http://10.0.0.12:8080/api/health
time_connect就很大 → DNS 或 TCP 建连慢(网络层);time_starttransfer(TTFB)大 → 后端处理慢(应用层);total远大于ttfb→ 响应体传输慢(带宽 / 大文件)。
**直连也是几十秒没响应 → 问题在后端,别在 Nginx 上折腾了。**这一步能省掉一半无效排查。
五、第 4 步:五类根因速查
- 后端自身慢 :慢 SQL、Full GC、下游接口拖累、锁等待。表现为
urt持续偏大,504 集中在某些接口上($request能看出规律)。 - 后端排队:worker 进程数不够、线程池 / 连接池打满,请求在 backlog 里排队。表现为并发一上来才超时,平时正常。
- Nginx 到后端的 keepalive 没配 :每个请求都重新建连,高并发下建连耗时叠加成超时。需要
upstream里加keepalive 64;,同时proxy_http_version 1.1;+proxy_set_header Connection "";,三者缺一不可。 - 长任务同步等待 :数据导出、报表生成、大文件下载硬等几十秒。这类请求本就不该同步等,应该改成异步任务 + 轮询/回调。
- 超时阈值配反了 :客户端超时 < Nginx 超时 < 后端超时。任何一层率先放弃,就会记成 499 或 504。正确顺序是:客户端 > Nginx > 后端应用,让最早发现问题的层来报错。
六、避坑清单
- 只调大
proxy_read_timeout是掩盖,不是解决------请求继续堆积,连接数、内存一起涨,只是爆炸时间往后推。 proxy_read_timeout要大于后端应用自身的超时,否则后端还在等下游,Nginx 已经先甩 504 了,日志里只剩一句超时。- 504 和 499 别混着下结论:499 通常意味着客户端先走人,检查前端 axios / 网关 / SLB 的超时设置。
- 云负载均衡(SLB / ALB)也有空闲超时,通常比 Nginx 更短,客户端到 Nginx 这一段先在 SLB 断掉,Nginx 日志里就是 499。
upstream_status是逗号分隔的多个值时,说明发生了重试 ,每个值对应upstream_addr里的一个后端,能直接看出是哪台在拖后腿。- 不要把
ping当证据:ICMP 通不代表 8080 端口通,也不代表后端应用没卡死。 - 两次采样之间要看趋势:偶发 504 去查后端 GC 与慢查询;持续 504 先查后端进程/线程池是否已经饱和。
小结
排查顺序记成一句话:先看 access log 的 $request_time / $upstream_response_time 分清慢在谁身上 → 读 error.log 的 upstream timed out 关键字区分"慢"还是"不通" → 直连后端端口复现,把 Nginx 摘出去 → 按"后端慢 / 排队 / keepalive / 长任务 / 阈值配反"五类根因对症处理。超时从来不是 Nginx 的错,它只是第一个把问题喊出声的人。
--- 关注本号,每天一个实用运维知识点 ---