服务器504超时错误处理

结论: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_timeout502(连接失败)
发送请求代理把请求体写给上游proxy_send_timeout502 / 504
等待响应代理等上游返回响应头/响应体proxy_read_timeout504

一句话区分:502 是"握手失败",504 是"等太久"。Nginx error.log 里 504 的典型原文是 upstream timed out (110: Connection timed out) while reading response header from upstream。

一分钟定位:确认是哪个环节的超时

结论:三条命令确定 504 的性质——看 error.log 的超时原文、看后端自身耗时、直连后端复现。关键是要区分"后端真的慢"和"参数设太短"。

bash
# 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 接近阈值按接口调大超时
数据库慢查询 / 锁等待并发上来后大量 504show 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_timeout60shttp/server/location与上游建连超时,超时报 502
proxy_send_timeout60s同上两次写操作之间的间隔
proxy_read_timeout60s同上两次读操作之间的间隔,504 主因
Nginx(FastCGI)fastcgi_connect_timeout60s同上连 PHP-FPM 超时
fastcgi_send_timeout60s同上发送请求超时
fastcgi_read_timeout60s同上等 PHP 响应超时,PHP 站点 504 主因
Nginx(uwsgi/scgi)uwsgi_read_timeout / scgi_read_timeout60s同上Python/uWSGI 场景对应参数
PHPmax_execution_time30sphp.ini / .user.ini脚本最长执行时间,CLI 默认为 0
request_terminate_timeout0(不限)www.confFPM 强制终止单个请求的秒数
request_slowlog_timeout0(关闭)www.conf慢请求阈值,配合 slowlog 记录堆栈
Java / Tomcatserver.tomcat.connection-timeout20sapplication.yml连接空闲超时
spring.mvc.async.request-timeout无application.yml异步请求超时
HikariCP connectionTimeout30sapplication.yml获取数据库连接的最大等待
Druid maxWait-1(无限)配置类默认无限等待,生产必须显式设置
数据库wait_timeout28800sMySQL空闲连接超时
innodb_lock_wait_timeout50sMySQL行锁等待超时,超时报 1205
max_execution_time0MySQL 8.0SELECT 最大执行毫秒数
云负载均衡读超时 / 空闲超时多为 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~30s15s超过即视为异常,应加缓存与索引
普通写接口(下单/提交)30~60s45s必须做幂等,避免超时重试重复扣款
复杂查询 / 搜索60~120s90s加分页、异步预计算
报表统计 / 数据导出180~300s240s强烈建议改为异步任务 + 结果下载
文件上传按大小与带宽估算300s+改用对象存储直传(STS/预签名)
流式响应(SSE/WebSocket)需 proxy_buffering off长连接单独配置 location,关闭缓冲

成因一:后端处理慢——先量化"慢在哪"

结论:后端慢必须量化到具体函数或 SQL,不能只看"接口慢"这个结论。推荐在 Nginx 日志里记录 $upstream_response_time 与 $request_time,两者差值能直接暴露是后端慢还是客户端慢。

nginx
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 计的是"两次读之间的间隔",不是总耗时——后端持续输出数据就不会触发。

nginx
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;
}

改完必须做语法检查与平滑重载:

bash
sudo nginx -t && sudo systemctl reload nginx