Nginx 504 网关超时:完整修复指南
504 Gateway Timeout 表示 Nginx 作为反向代理时,已经成功连接到上游服务器(如 PHP-FPM、Node.js、Gunicorn),但上游在规定时间内没有返回完整响应。与 502 不同,502 是上游连接失败或返回无效响应,而 504 是连接成功了但响应太慢。
这个错误通常有两个层面的原因:一是 Nginx 的超时设置过短,无法等待慢请求完成;二是上游应用本身处理速度太慢,需要优化。本文将从日志分析到缓存配置,逐步解决 504 错误。
步骤 1:识别超时请求(Nginx 错误日志分析)
首先从 Nginx 错误日志中定位哪些请求触发了 504。日志会明确显示超时发生的上游地址和请求 URI:
# 查看 Nginx 错误日志中的 504 记录
grep "504\|upstream timed out" /var/log/nginx/error.log | tail -20
# 典型日志输出示例:
# [error] 12345#0: *6789 upstream timed out
# (110: Connection timed out) while reading response header
# from upstream, client: 1.2.3.4,
# server: example.com, request: "GET /api/heavy-query HTTP/1.1",
# upstream: "fastcgi://127.0.0.1:9000"
从日志中提取关键信息:upstream 字段显示了超时的后端地址,request 字段显示了具体是哪个 URL 触发了超时。如果只有特定接口频繁超时,问题很可能是该接口的后端逻辑需要优化,而非全局超时设置问题。
步骤 2:增加代理超时设置
如果后端处理确实需要较长时间(如大数据导出、复杂报表生成),可以通过调整 Nginx 超时参数来解决。默认的 proxy_read_timeout 为 60 秒,对于慢请求可能不够:
# 在 server 或 location 块中添加
location /api/ {
proxy_pass http://backend;
# 连接上游的超时时间
proxy_connect_timeout 30s;
# 向上游发送请求的超时时间
proxy_send_timeout 60s;
# 读取上游响应的超时时间(504 的关键参数)
proxy_read_timeout 300s;
# 对于 FastCGI(PHP-FPM)使用对应指令
# fastcgi_read_timeout 300s;
# fastcgi_connect_timeout 30s;
# fastcgi_send_timeout 60s;
}
修改后测试配置并重载 Nginx:
sudo nginx -t
sudo nginx -s reload
注意:增大超时只是治标之策。如果后端响应需要 300 秒以上,应该考虑异步处理、任务队列或将长耗时操作拆分为多个短请求。
步骤 3:检查上游应用性能
504 的根本原因往往是上游应用处理太慢。定位慢请求的具体瓶颈:
# 对于 PHP 应用,启用慢日志
# 在 php-fpm.conf 或 pool.d/www.conf 中添加:
# slowlog = /var/log/php-fpm/slow.log
# request_slowlog_timeout = 5s
# 查看慢日志
tail -50 /var/log/php-fpm/slow.log
# 对于 Node.js 应用,使用 --prof 生成性能分析
node --prof app.js
# 对于 Python/Django,启用 SQL 查询日志
# 在 settings.py 中检查 DATABASES 配置
# 确保 CONN_MAX_AGE 设置合理
常见的后端性能瓶颈包括:数据库慢查询(缺少索引、全表扫描)、外部 API 调用超时(第三方支付、短信网关)、内存溢出导致频繁垃圾回收、以及文件 I/O 阻塞。使用应用性能监控工具(如 New Relic、Datadog)可以帮助持续追踪慢请求。
步骤 4:配置 Nginx upstream keepalive
每次请求都重新建立 TCP 连接会带来额外开销,在高并发场景下可能间接导致 504。配置 keepalive 连接池可以复用已有连接,减少握手延迟:
upstream backend {
server 127.0.0.1:8080;
# 保持最多 32 个空闲连接
keepalive 32;
# 空闲连接超时时间(默认 60s)
keepalive_timeout 60s;
}
server {
location / {
proxy_pass http://backend;
# 必须设置 HTTP/1.1 才能使用 keepalive
proxy_http_version 1.1;
# 清除 Connection 头,使连接可复用
proxy_set_header Connection "";
proxy_read_timeout 300s;
}
}
配置 keepalive 后,Nginx 会维护一个到上游的持久连接池,避免频繁创建和销毁 TCP 连接。这在 PHP-FPM 使用 TCP 模式(而非 Unix socket)时效果尤为明显。
步骤 5:实施缓存
对于响应较慢但内容变化不频繁的接口,配置 Nginx 缓存可以大幅减轻后端压力,从根本上预防 504:
# 在 http 块中定义缓存区域
proxy_cache_path /var/cache/nginx
levels=1:2
keys_zone=api_cache:10m
max_size=1g
inactive=10m
use_temp_path=off;
server {
location /api/ {
proxy_pass http://backend;
proxy_cache api_cache;
# 缓存 200 响应 5 分钟
proxy_cache_valid 200 5m;
# 不缓存错误响应
proxy_cache_valid 404 1m;
# 添加缓存命中状态头
add_header X-Cache-Status $upstream_cache_status;
}
}
# 创建缓存目录并设置权限
sudo mkdir -p /var/cache/nginx
sudo chown nginx:nginx /var/cache/nginx
sudo nginx -t && sudo nginx -s reload
启用缓存后,首次请求仍然会到达后端,但后续相同请求将直接从 Nginx 缓存返回,响应时间从秒级降至毫秒级,大幅降低 504 发生概率。
快速参考:原因与修复
| 症状 / 日志信息 | 根本原因 | 修复方法 |
|---|---|---|
upstream timed out (110) |
proxy_read_timeout 设置过短 | 增大 proxy_read_timeout 至 300s 或更长 |
| 仅特定 API 接口超时 | 后端处理逻辑过慢(慢查询、外部调用) | 优化数据库查询、添加索引或使用任务队列 |
| 高并发时频繁 504 | 连接池耗尽,每请求新建 TCP 连接 | 配置 upstream keepalive 和 proxy_http_version 1.1 |
| 504 伴随高 CPU 使用率 | 后端应用 CPU 密集型计算 | 增加 worker 进程数、优化算法或水平扩展 |
| 数据库查询导致的 504 | 慢 SQL 查询阻塞响应 | 添加数据库索引、优化查询或启用查询缓存 |
| 间歇性 504,负载正常 | 外部 API 调用偶尔超时 | 为外部调用设置超时、添加重试机制或降级策略 |