结论:504 Gateway Timeout 的本质是「代理服务器已经连上上游,但在超时时间内没等到响应」——关键词是超时,不是连接失败。处理顺序是:从 Nginx error.log 确认 upstream timed out 及超时秒数 → 判断是后端真的慢还是超时参数设太短 → 用 show processlist、slowlog 定位慢点 → 按业务类型设置合理超时(普通 API 60 秒、报表导出 300 秒)→ 长任务改为异步。
504 几乎总是"慢"的代名词,而"慢"比"挂"更难排查:服务是活的、端口是通的、日志也正常,用户却一直转圈最后看到一个灰白的超时页。更麻烦的是,代理层超时后后端往往还在继续执行,如果这个请求是下单或扣款,就可能出现"用户看到失败、实际已成功"的重复执行问题。本文把 504 的六类成因、全链路超时参数位置、定位命令与根治方案一次讲清。
504 的本质:连上了,但等太久
结论:504 发生在"等待响应"阶段,此时 TCP 三次握手与请求转发都已成功,只是上游迟迟不返回。理解这一点,就能明白为什么 504 的排查重点是"谁慢"而不是"谁挂"。
一次经过反向代理的请求会经过三个超时窗口,它们分别对应不同的状态码:
| 阶段 | 说明 | 对应 Nginx 参数 | 超时后返回 |
|---|---|---|---|
| 建立连接 | 代理与上游做 TCP 握手 | proxy_connect_timeout | 502(连接失败) |
| 发送请求 | 代理把请求体写给上游 | proxy_send_timeout | 502 / 504 |
| 等待响应 | 代理等上游返回响应头/响应体 | proxy_read_timeout | 504 |
一句话区分:502 是"握手失败",504 是"等太久"。Nginx error.log 里 504 的典型原文是 upstream timed out (110: Connection timed out) while reading response header from upstream。
一分钟定位:确认是哪个环节的超时
结论:三条命令确定 504 的性质——看 error.log 的超时原文、看后端自身耗时、直连后端复现。关键是要区分"后端真的慢"和"参数设太短"。
# 1)确认 Nginx 侧超时点与超时秒数
sudo grep -E 'upstream timed out|upstream prematurely|while reading response' \
/var/log/nginx/error.log | tail -20
# 2)确认后端自身处理耗时(Nginx 日志加 $upstream_response_time)
sudo tail -100 /var/log/nginx/access.log | awk '{print $NF}' | sort -rn | head -5
# 3)绕开 Nginx 直连后端,用 time 测量真实耗时
time curl -s -o /dev/null -w "HTTP=%{http_code} 总耗时=%{time_total}s\n" \
-X POST http://127.0.0.1:8080/api/order -d 'test=1'判断逻辑:
- 直连后端也要 70 秒才返回 → 后端真的慢,去查慢查询、慢接口、外部调用。
- 直连后端 3 秒就返回,但走 Nginx 就 504 → 超时参数或缓冲区问题,或该请求走了不同的 upstream。
- 只在高峰期 504 → 资源竞争、线程池/连接池耗尽、数据库锁等待。
- 只有特定接口 504 → 该接口自身逻辑问题,与全局参数无关。
504 成因对照表:六类问题
结论:504 的成因集中在"慢"上,按后端慢、参数短、数据库、外部依赖、语言层限制、资源耗尽六类归纳。下表可直接按现象对号入座。
| 成因 | 典型现象 | 关键证据 | 修复方向 |
|---|---|---|---|
| 后端处理慢 | 特定接口慢,低并发也慢 | 后端耗时 > 超时阈值 | 优化代码、加索引、加缓存 |
proxy_read_timeout 过短 | 后端要 90 秒,参数只有 60 秒 | $upstream_response_time 接近阈值 | 按接口调大超时 |
| 数据库慢查询 / 锁等待 | 并发上来后大量 504 | show processlist 有大量 Sending data / Locked | 加索引、拆事务、降隔离级别 |
| 外部 API 调用阻塞 | 依赖第三方时必现 | 抓包/日志显示卡在外部请求 | 设外部超时、熔断、异步 |
PHP max_execution_time / FPM 终止 | PHP 站点 30 秒左右必断 | php-fpm slowlog | 调 request_terminate_timeout |
| 后端线程池 / 连接池耗尽 | 高峰期集体 504,后端 CPU 不高 | 连接数打满、等待队列积压 | 扩线程、扩实例、限流 |
| 反向代理与云 LB 双层超时不一致 | 后端 40 秒返回却被 504 | 云监控显示 LB 超时 | 对齐各层超时值 |
全链路超时参数位置表:一层一层对齐
结论:504 排查的核心动作是"把链路上每一层的超时值列出来,确保上游阈值 < 下游阈值"。只要有一层没改到,其他层改了也没用。
| 层级 | 参数 | 默认值 | 位置 | 说明 |
|---|---|---|---|---|
| Nginx(HTTP 代理) | proxy_connect_timeout | 60s | http/server/location | 与上游建连超时,超时报 502 |
proxy_send_timeout | 60s | 同上 | 两次写操作之间的间隔 | |
proxy_read_timeout | 60s | 同上 | 两次读操作之间的间隔,504 主因 | |
| Nginx(FastCGI) | fastcgi_connect_timeout | 60s | 同上 | 连 PHP-FPM 超时 |
fastcgi_send_timeout | 60s | 同上 | 发送请求超时 | |
fastcgi_read_timeout | 60s | 同上 | 等 PHP 响应超时,PHP 站点 504 主因 | |
| Nginx(uwsgi/scgi) | uwsgi_read_timeout / scgi_read_timeout | 60s | 同上 | Python/uWSGI 场景对应参数 |
| PHP | max_execution_time | 30s | php.ini / .user.ini | 脚本最长执行时间,CLI 默认为 0 |
request_terminate_timeout | 0(不限) | www.conf | FPM 强制终止单个请求的秒数 | |
request_slowlog_timeout | 0(关闭) | www.conf | 慢请求阈值,配合 slowlog 记录堆栈 | |
| Java / Tomcat | server.tomcat.connection-timeout | 20s | application.yml | 连接空闲超时 |
spring.mvc.async.request-timeout | 无 | application.yml | 异步请求超时 | |
HikariCP connectionTimeout | 30s | application.yml | 获取数据库连接的最大等待 | |
Druid maxWait | -1(无限) | 配置类 | 默认无限等待,生产必须显式设置 | |
| 数据库 | wait_timeout | 28800s | MySQL | 空闲连接超时 |
innodb_lock_wait_timeout | 50s | MySQL | 行锁等待超时,超时报 1205 | |
max_execution_time | 0 | MySQL 8.0 | SELECT 最大执行毫秒数 | |
| 云负载均衡 | 读超时 / 空闲超时 | 多为 60s | 控制台 | 阿里云 CLB、腾讯云 CLB 默认约 60s,AWS ALB idle_timeout 默认 60s(截至 2026 年以控制台实际值为准) |
| CDN | 回源超时 | 多为 30s | 控制台 | 静态资源走 CDN 时的额外一层 |
对齐原则:从外到内依次增大,例如 CDN 回源 60s → 云 LB 读超时 90s → Nginx proxy_read_timeout 120s → 后端自身超时 180s。这样能保证"谁先超时"是可预期的,通常让最外层的客户端侧先超时反而更好,因为后端可以继续完成。
超时参数合理取值建议
结论:不要无脑调大,按业务类型分层设置——普通 API 60 秒、查询类 120 秒、报表/导出类 300 秒。超时值越大,占用 worker 的时间越长,并发能力越差,因此"调大超时"永远是权宜之计而非根治方案。
| 业务类型 | 建议 proxy_read_timeout | 建议后端超时 | 配套措施 |
|---|---|---|---|
| 普通读接口(列表/详情) | 10~30s | 15s | 超过即视为异常,应加缓存与索引 |
| 普通写接口(下单/提交) | 30~60s | 45s | 必须做幂等,避免超时重试重复扣款 |
| 复杂查询 / 搜索 | 60~120s | 90s | 加分页、异步预计算 |
| 报表统计 / 数据导出 | 180~300s | 240s | 强烈建议改为异步任务 + 结果下载 |
| 文件上传 | 按大小与带宽估算 | 300s+ | 改用对象存储直传(STS/预签名) |
| 流式响应(SSE/WebSocket) | 需 proxy_buffering off | 长连接 | 单独配置 location,关闭缓冲 |
成因一:后端处理慢——先量化"慢在哪"
结论:后端慢必须量化到具体函数或 SQL,不能只看"接口慢"这个结论。推荐在 Nginx 日志里记录 $upstream_response_time 与 $request_time,两者差值能直接暴露是后端慢还是客户端慢。
log_format main '$remote_addr - $request_time - $upstream_response_time '
'$status $request "$http_referer" "$http_user_agent"';
access_log /var/log/nginx/access.log main;$request_time 明显大于 $upstream_response_time,说明时间花在接收客户端请求体或发送响应上(客户端网络慢);两者接近且都大,说明后端处理慢。
成因二:proxy_read_timeout 设置过短
结论:Nginx 侧补全三个超时参数并重载,是最快的缓解手段。注意 proxy_read_timeout 计的是"两次读之间的间隔",不是总耗时——后端持续输出数据就不会触发。
location /api/ {
proxy_pass http://backend;
# 建连 5 秒足够(同机/同内网)
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 120s;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 报表导出单独放宽,避免影响其他接口
location /api/report/ {
proxy_pass http://backend;
proxy_connect_timeout 5s;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
# PHP-FPM 场景用 fastcgi_* 系列
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_connect_timeout 5s;
fastcgi_send_timeout 60s;
fastcgi_read_timeout 120s;
include fastcgi_params;
}改完必须做语法检查与平滑重载:
sudo nginx -t && sudo systemctl reload nginx
企业QQ咨询




