如果说 502 是"连不上后端",那 504 Gateway Timeout 就是"连上了后端,但后端太久没回话"。用户看到的可能是页面转了十几秒甚至半分钟后弹出一个 504,也可能某些接口一到高峰期就必现 504。和 502 相比,504 的迷惑性更强——因为后端进程明明活着,端口也在监听,ps 一看一切正常,可它就是慢。
这篇文章要讲清 504 的成因和排查。核心观点先摆出来:504 是"超时"的结果,但超时只是表象,真正要治的是"后端为什么慢"。只把 Nginx 的 proxy_read_timeout 调大,等于把报警器音量调小,问题依然在,只是晚一点暴露、或者换一种方式暴露(比如连接堆积、内存耗尽)。所以我们要分两步走:先搞清楚 Nginx 里哪几个超时参数控制 504,再深入后端找出慢的真正原因(慢 SQL、外部接口、锁等待、资源打满),最后给出治本的优化方向。
一、504 到底卡在哪一步
一次请求经过 Nginx 到后端,涉及好几个"等待"环节,任何一个超过设定的时间,Nginx 就会返回 504:
客户端 --请求--> Nginx --连接--> 后端 --处理(SQL/外部调用/计算)--> 返回
| | |
client_timeout connect read/send timeout
- 连接阶段:Nginx 去连后端,如果后端因为连接数满、负载太高而迟迟不接受连接,超过
proxy_connect_timeout就可能报错。 - 读写阶段:连接建立了,Nginx 把请求发给后端、等后端返回。如果后端处理太久、迟迟不吐数据,超过
proxy_read_timeout(或 FastCGI 的fastcgi_read_timeout),Nginx 返回 504。
所以 504 通常指向"后端处理慢"。区分 502 和 504 的一句话记忆法:502 是"找不到/连不上后端",504 是"后端太慢"。
二、Nginx 侧的超时参数
先把控制超时的几个参数理清楚。不同代理方式用的参数名不同:
| 场景 | 参数 | 默认值 | 含义 |
|---|---|---|---|
| 反向代理 | proxy_connect_timeout |
60s | 与后端建立连接的超时 |
| 反向代理 | proxy_read_timeout |
60s | 两次读操作之间的间隔超时(等后端响应) |
| 反向代理 | proxy_send_timeout |
60s | 向后端发送请求的超时 |
| FastCGI | fastcgi_connect_timeout |
60s | 连 PHP-FPM 的超时 |
| FastCGI | fastcgi_read_timeout |
60s | 等 PHP-FPM 响应的超时 |
| FastCGI | fastcgi_send_timeout |
60s | 向 PHP-FPM 发送的超时 |
| 客户端 | client_body_timeout |
60s | 读客户端请求体的超时 |
| 客户端 | client_header_timeout |
60s | 读客户端请求头的超时 |
| 保持连接 | keepalive_timeout |
75s | 与客户端空闲连接保持时间 |
配置示例:
location /api/ {
proxy_pass http://backend;
proxy_connect_timeout 5s; # 连后端要快,5 秒够了
proxy_send_timeout 30s;
proxy_read_timeout 60s; # 后端处理上限,按业务合理设置
# 如果后端确实需要长任务,再单独对特定 location 放宽
# 但必须配合后端自身的超时设置,不能只调 Nginx
}
# FastCGI/PHP 场景
location ~ \.php$ {
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_read_timeout 60s; # 以及 PHP 侧的 max_execution_time
fastcgi_connect_timeout 5s;
}
关键提醒:这些参数不是越大越好。如果后端在一个请求里要跑 5 分钟,而你把 proxy_read_timeout 提到 300s,看似不报 504 了,但这 5 分钟里 Nginx 与该连接一直占着资源,高并发下会迅速堆积,最终引发别的故障。所以调大超时只能是"给真正需要长任务的接口"用的兜底,不能当常规解法。
另外,超时是"两端"的。Nginx 放宽了,后端(比如 PHP 的 max_execution_time、Gunicorn 的 timeout、Tomcat 的 connectionTimeout、MySQL 的 wait_timeout)也要匹配,否则会出现"Nginx 还没超时,后端先自己断了"或反过来,返回 502 而不是 504。
三、504 的真正根因:后端为什么慢
把 Nginx 侧看明白后,重点全在"后端为什么慢"。按出现频率,大致有这几类。
根因一:慢 SQL / 数据库查询
这是最常见的一类。某个接口的 SQL 没走索引、或者数据量涨上来后查询变慢,几秒甚至几十秒才返回,Nginx 等不及就 504。
# MySQL:打开慢查询日志
mysql -e "SHOW VARIABLES LIKE 'slow_query_log';"
mysql -e "SHOW VARIABLES LIKE 'long_query_time';"
# 看当前正在执行的、可能卡住的查询
mysql -e "SHOW FULL PROCESSLIST;"
# 按时间排序找跑得最久的
mysql -e "SELECT id,user,time,state,LEFT(info,80) FROM information_schema.processlist ORDER BY time DESC LIMIT 10;"
# 看锁等待与死锁
mysql -e "SHOW ENGINE INNODB STATUS\G" | sed -n '/LATEST DETECTED DEADLOCK/,/^---/p'
判读:PROCESSLIST 里 Time 很大、State 是 Sending data/Waiting for table metadata lock/Locked 的,就是嫌疑对象。常见原因是全表扫描、缺索引、大批量更新、DDL 阻塞、事务未提交长期持锁。
治本做法:给慢查询加索引、优化 SQL、拆大事务、加读缓存、必要时做读写分离或分库分表。调大超时没用。
根因二:外部接口调用慢
很多接口会调用第三方 API(支付、短信、地图、风控),如果对方响应慢或者超时没设好,你的接口就一直卡在那里等。
# 从服务器测外部接口的响应时间
curl -o /dev/null -s -w 'dns:%{time_namelookup}s connect:%{time_connect}s total:%{time_total}s\n' \
https://api.partner.example.com/endpoint
# 抓包看是否卡在某个外部连接(需 tcpdump)
tcpdump -i any -nn -c 50 host api.partner.example.com
治本做法:给所有外部调用设独立超时 + 重试 + 降级。外部接口慢不能拖垮自己的接口,要么快速失败、要么返回兜底数据,绝不能无限等。
根因三:锁等待
应用层的锁(分布式锁、文件锁)或数据库的锁,会让请求排队。表现是并发一高就集体变慢,最后大批 504。
# 看有没有大量进程在等锁(应用若用 flock,可用 lsof 看)
lsof /path/to/lockfile
# 数据库侧的元数据锁等待(MySQL 8)
mysql -e "SELECT * FROM performance_schema.metadata_locks;"
mysql -e "SELECT * FROM sys.innodb_lock_waits\G"
治本做法:缩短持锁时间、避免长事务、把热点数据移到锁更细的存储、或引入队列削峰。
根因四:后端资源打满
CPU 打满、内存不足导致频繁 swap、连接池耗尽,都会让处理变慢。
# 系统整体
top -bn1 | head -20
free -h
vmstat 1 5 # 看 si/so 是否在频繁换页,r 列(运行队列)是否过大
# 连接池/线程池打满(以应用日志为准)
grep -iE 'pool|timeout|exhaust' /var/log/myapp/*.log | tail -20
# Java 应用看 GC 是否频繁(频繁 Full GC 会让接口变慢)
jstat -gcutil 1000 10
治本做法:扩容、优化代码、限制单请求资源、给 GC 调参、加大连接池并排查连接泄漏。
根因五:网络层面的慢
后端和数据库跨机房、跨可用区,网络抖动或带宽打满,也会让"处理时间"变长。
# 测到数据库/依赖的网络质量
mtr -rw -c 20 10.0.0.5
# 看接口的耗时分布(是否大部分耗在网络)
根因分类对照
| 根因 | 典型特征 | 定位手段 | 治本方向 |
|---|---|---|---|
| 慢 SQL | 数据库 CPU 高、有慢查询 | SHOW PROCESSLIST、慢日志 |
加索引、优化 SQL |
| 外部接口慢 | 特定接口偶发超时 | curl 计时、抓包 | 设超时+重试+降级 |
| 锁等待 | 并发高时集中变慢 | 锁等待查询、应用日志 | 缩短持锁、削峰 |
| 资源打满 | CPU/内存/连接池触顶 | top、free、连接池日志 |
扩容、优化、调参 |
| 网络慢 | 跨机房依赖慢 | mtr、接口耗时拆分 |
就近部署、专线 |
四、把"慢"量化:先测再改
排查 504 有个非常实用的习惯:先量化每个环节的耗时,别凭感觉。
# 1. 从客户端侧看整体耗时(TTFB 是重点)
curl -o /dev/null -s -w 'total:%{time_total}s ttfb:%{time_starttransfer}s\n' https://www.example.com/api/slow
# 2. 在服务器本地请求,排除网络干扰
curl -o /dev/null -s -w 'total:%{time_total}s ttfb:%{time_starttransfer}s\n' http://127.0.0.1:8080/api/slow
# 3. 看 Nginx 访问日志里的 upstream 耗时(需自定义 log_format)
# 配置里加 $upstream_response_time $request_time
Nginx 的日志加上 $upstream_response_time(后端处理耗时)和 $request_time(Nginx 整体处理耗时)后,能一眼看出到底是后端慢还是 Nginx 侧慢:
log_format timed '$remote_addr "$request" status=$status '
'req_time=$request_time up_time=$upstream_response_time';
access_log /var/log/nginx/access.log timed;
如果 up_time 很大而 req_time 差不多,说明慢在后端;如果 req_time 远大于 up_time,可能是客户端慢或传输慢。
五、对症的药:不能只调超时
很多人遇到 504 的第一反应是"把超时改大",这在短期止血上没错,但必须配合治本:
- 短期止血:对确实需要较长处理的特定接口,单独放宽
proxy_read_timeout,并同步放宽后端对应超时。这是给用户一个缓冲,不是解决。 - 定位根因:用第三节的方法,确认是 SQL、外部调用、锁还是资源问题。
- 治本优化:加索引、缓存、异步化、削峰、扩容、设超时与降级。
- 异步化长任务:对于本来就要跑很久的任务(导出报表、批量处理),正确的架构是"提交任务 → 立即返回任务 ID → 后台异步执行 → 前端轮询结果",而不是让 HTTP 请求一直挂着等。这是根治 504 最有效的思路之一。
- 加监控告警:对接口的 P95/P99 延迟设阈值告警,在用户大面积遇到 504 之前就发现趋势。
六、超时参数的常见误区
- 只调 Nginx 不调后端:后端先超时了,用户看到的可能是 502 或后端自定义错误,而不是 504,排查时会对不上。
- 所有接口用同一个超时:快接口和慢接口应区别对待。导出接口可以 120s,登录接口 60s 都嫌长,应设 5–10s。
- 超时设得极大:
proxy_read_timeout 600s会让故障时大量连接长时间占用内存,可能引发雪崩。宁可快速失败 + 重试。 - 忽视连接池:后端连接池小于 Nginx 并发时,多余的请求在排队,表现为 504。要保证连接池容量与并发匹配。
- 忘了 keepalive:应用间不开长连接,每次请求都重新握手,高并发下额外开销也会拖慢响应。
常见问题
Q:504 和 502 怎么快速区分?
看 Nginx error.log:出现 upstream timed out 的是 504,connect() failed/Connection refused 的是 502。含义上,502 偏"连不上后端",504 偏"后端太慢"。
Q:把 proxy_read_timeout 调到 300s 后偶尔不报 504 了,但用户还是抱怨慢,怎么办?
因为根因没解决,用户依然要等很久。调大超时只是让 Nginx 别提前掐断,用户等待时间没变短。要继续查后端慢的原因。
Q:PHP 站点经常 504,重点看哪里?
看 PHP-FPM 日志和 PHP 的 max_execution_time,以及是否有脚本在做大数据量操作。可能是某个脚本执行太久,也可能是 FPM 进程池太小导致排队。两处都要看。
Q:只有个别接口 504,其他正常,说明什么?
说明是那个接口自身的问题(慢 SQL、外部调用、算法复杂),不是全局资源或网络问题。直接针对该接口做链路计时和代码/SQL 分析。
Q:加了 proxy_next_upstream timeout 后 504 变少了,可以长期这样吗?
重试能减少用户感知,但如果根因是后端整体慢,重试会把请求再打一遍,加重后端负担,可能雪上加霜。重试只适用于"多上游中个别慢"的场景,且有幂等性要求,不能滥用。
Q:云负载均衡(SLB/ELB)也报 504,和 Nginx 504 一样吗?
类似。负载均衡到后端也有自己的超时(常见默认 60s),如果它设得比 Nginx 小,会先报 504。要一并检查负载均衡、Nginx、后端应用三层超时是否协调(一般从外到内逐层放宽,或统一)。
总结
504 Gateway Timeout 的含义是"网关等后端响应超时"。它由 Nginx 返回,但病根几乎总在后端"处理太慢"。
Nginx 侧涉及的超时参数主要有 proxy_connect_timeout、proxy_read_timeout、proxy_send_timeout(FastCGI 对应 fastcgi_*)。这些参数控制"等待多久",不控制"为什么慢"。
后端的根因按频率排序是:慢 SQL、外部接口慢、锁等待、资源打满、网络慢。用 SHOW PROCESSLIST、慢日志、curl 计时、top/free、mtr 等手段逐一验证。
调大超时只能短期止血,治本要靠优化 SQL、给外部调用设超时与降级、把长任务异步化、扩容和加监控。超时分层设置、连接池容量匹配、保持长连接,是减少 504 的关键工程手段。
企业QQ咨询




