解决 Nginx 504 网关超时与客户端超时问题

掌握 Nginx 超时问题,包括令人头疼的 504 网关超时,学习调整关键的代理指令。本指南详细介绍了如何增加 `proxy_read_timeout`、优化缓冲以及使用错误日志诊断 Nginx 与上游服务器之间的通信故障,以实现稳健的连接处理。

解决 Nginx 504 网关超时与客户端超时问题

Nginx 的 504 Gateway Timeout 错误意味着 Nginx 作为代理或网关,未能在规定时间内从上游服务获得响应。上游服务可能是 Node.js 应用、Gunicorn、PHP-FPM、另一个 Nginx 服务、内部 API 或负载均衡器。

一个诱人的修复方法是将所有超时时间提高到五分钟,然后继续。有时,更长的超时时间是正确的短期举措,特别是对于报表、导入或仅管理员任务。但是,如果普通用户请求需要的时间超过 Nginx 当前允许的时间,您还应该询问后端为何缓慢、请求是否应该异步处理,以及路径中的其他代理是否设置了更短的超时时间。


理解 504 网关超时错误

当 Nginx 作为反向代理或网关,未能在规定时间内从它转发请求的上游服务器收到响应时,就会发生 504 Gateway Timeout 错误。简单来说:Nginx 向后端询问答案,等待了配置的时间,由于没有收到响应而放弃。

这与 502 Bad Gateway(Nginx 收到无效或过早关闭的上游响应)和 503 Service Unavailable(通常表示服务故意不可用或过载)不同。这种区别很重要,因为 504 错误将您引向等待、超时和上游延迟的问题。

控制上游超时的关键指令

当代理请求时,Nginx 使用几个关键指令,主要位于 httpserverlocation 块内,或者专门位于 upstream 块内。调整这些值是解决 504 错误的主要方法。

1. proxy_connect_timeout

此指令设置与上游服务器建立连接的超时时间。如果 Nginx 在此期间无法连接,则返回超时错误。

默认值: 60 秒

proxy_connect_timeout 60s;

2. proxy_send_timeout

此指令设置向上游服务器连续两次写入操作之间的超时时间。这与发送大型请求体相关。

默认值: 60 秒

proxy_send_timeout 60s;

3. proxy_read_timeout(解决 504 错误的最常见方法)

此指令设置在发送请求头后等待上游服务器响应的超时时间。如果后端应用程序处理请求并生成响应体花费的时间过长,则需要增加此指令的值。

默认值: 60 秒

# 示例:为慢速 API 将读取超时时间增加到 120 秒
proxy_read_timeout 120s;

如果您的应用程序经常超过默认值,请谨慎增加此值并继续调查。非常高的值可能会使客户端连接保持打开状态,而后端已经处于不健康状态。


处理客户端超时

客户端超时是一种不同的故障。浏览器、移动应用、负载均衡器、CDN 或调用服务在 Nginx 完成响应之前放弃。在这种情况下,用户可能会看到浏览器错误或来自 Nginx 前面层的网关错误,而 Nginx 可能会记录一个关闭的连接,而不是一个干净的 504 错误。

如果您在 Nginx 记录 504 错误之前遇到客户端超时,则需要检查客户端和 Nginx 之间的连接。

1. 客户端 Keepalive

如果客户端过早关闭连接,Nginx 可能会收到错误,或者客户端可能只是等待数据超时。

如果客户端是另一个代理或负载均衡器,请根据 Nginx 和后端检查其超时设置。链中最短的超时时间通常决定最终结果。一个常见的模式是:CDN 等待 100 秒,负载均衡器等待 60 秒,Nginx 等待 180 秒,后端需要 120 秒。用户仍然会在 60 秒时失败,因为负载均衡器首先放弃。

2. Nginx send_timeout

此指令控制 Nginx 等待客户端确认或接收数据的时间(连续两次向客户端写入操作之间的时间)。

默认值: 60 秒

# 如果客户端在 Nginx 发送响应时超时,请设置此项
send_timeout 120s;

优化大响应的缓冲

有时后端开始响应,但传输仍然缓慢,因为响应很大、客户端很慢,或者 Nginx 必须缓冲超出预期的数据。这在生成的 CSV 导出、通过应用路由的媒体下载或返回非常大 JSON 负载的 API 中很常见。

Nginx 使用缓冲区临时保存从上游接收的数据,然后再发送给客户端。如果响应非常大,这些缓冲区可能会被超出,导致处理复杂或感知延迟。

关键缓冲指令

这些通常设置在 location 块或 server 块内:

指令 目的
proxy_buffers 设置用于从上游读取响应的缓冲区的数量和大小。格式:number size;
proxy_buffer_size 设置第一个缓冲区的大小,用于读取响应头。
proxy_max_temp_file_size 如果响应超出可用缓冲区,Nginx 会写入临时文件。此设置设置这些临时文件的最大大小。

高流量/大响应的示例配置:

location /api/heavy_report {
    proxy_pass http://backend_app;

    # 增加读取超时时间
    proxy_read_timeout 180s;

    # 为可能较大的响应体调整缓冲
    # 使用 8 个缓冲区,每个最大 1MB (1024k)
    proxy_buffers 8 1024k;
    proxy_buffer_size 256k;

    # 如果缓冲区溢出,允许临时文件最大 500MB
    proxy_max_temp_file_size 500m;
}

如果您的后端响应确实很大,请考虑从对象存储或静态存储提供生成的文件,而不是通过应用保持请求打开。对于导出,一种常见的模式是:将任务加入队列,生成文件,然后让用户在文件准备好时从静态 URL 下载。


故障排除步骤和日志分析

解决超时问题需要精确定位停滞发生的位置:客户端 -> Nginx,或 Nginx -> 后端。

步骤 1:检查 Nginx 错误日志

Nginx 错误日志是确定 Nginx 是否因等待后端而超时的决定性来源。

查找包含以下短语的条目:

  • upstream timed out (110: Connection timed out)
  • upstream prematurely closed connection while reading response header from upstream

如果您看到这些,问题在于 proxy_read_timeout 或后端的处理时间。

还要查找 client prematurely closed connection。这通常意味着客户端或 Nginx 前面的代理首先放弃。在这种情况下,仅提高 proxy_read_timeout 对用户没有帮助。

步骤 2:检查后端应用程序日志

如果 Nginx 超时(日志指示 504),请立即检查上游服务(例如,PHP-FPM 日志、Gunicorn 日志、Java 应用服务器日志)的日志。您需要确认请求是否到达后端以及完成所需的时间。

  • 如果后端日志显示请求花费的时间长于您配置的 proxy_read_timeout,请增加 Nginx 超时时间。
  • 如果后端日志显示请求快速完成,问题可能是 Nginx 和后端之间的网络延迟,或者面向 Nginx 的客户端超时配置错误。

步骤 3:使用 X-Upstream-Response-Time 头(可选)

对于详细的诊断,您可以使用 $upstream_response_time 变量在访问日志格式中记录上游响应的确切时间。这有助于确认后端的实际性能。

在您的 nginx.conf 中:

log_format proxy_detailed '$remote_addr - $remote_user [$time_local] "$request" '
                        '$status $body_bytes_sent "$http_referer" '
                        '"$http_user_agent" $request_time $upstream_response_time';

access_log /var/log/nginx/access.log proxy_detailed;

通过分析 $upstream_response_time,您可以查看 Nginx 等待的精确持续时间,与 Nginx 自身的超时设置无关。

对于快速的一次性测试,从 Nginx 主机直接调用上游:

time curl -sS -o /dev/null -w 'status=%{http_code} total=%{time_total}\n' http://127.0.0.1:3000/slow-route

如果直接上游调用已经很慢,Nginx 只是在报告问题。如果直接调用很快但代理请求超时,请检查代理配置、DNS 解析、容器网络、内部服务之间的 TLS,或 Nginx 和应用之间的其他跳点。


应用最小的有用更改

一个合理的生产修复通常如下所示:

location /api/reports/ {
    proxy_pass http://backend_app;
    proxy_connect_timeout 10s;
    proxy_send_timeout 60s;
    proxy_read_timeout 180s;
}

这仅针对慢速报表端点提高了读取超时时间。它不会让站点上的每个请求都等待三分钟。对于登录路由、结账路由、健康检查或公共 API 端点,长时间的超时可能会使故障更加痛苦,因为客户端会为不太可能恢复的请求等待更长时间。

对于 PHP-FPM,等效操作可能涉及 FastCGI 指令:

location ~ \.php$ {
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    fastcgi_read_timeout 120s;
    include fastcgi_params;
}

请记住,PHP、Python、Node.js、应用服务器、队列、数据库、CDN 和负载均衡器都可能有自己的超时设置。Nginx 无法在后端自身的 worker 超时终止请求后让后端继续工作。

在进行任何配置更改(例如,增加超时时间或调整缓冲区大小)后,始终测试配置语法并重新加载 Nginx:

sudo nginx -t
sudo systemctl reload nginx

然后,在重复相同请求的同时,观察 Nginx 和上游日志:

sudo tail -f /var/log/nginx/error.log

最好的超时修复会让您清楚地了解更改的原因:此路由合法地需要最多两分钟,此上游现在具有匹配的限制,并且慢速请求在日志或指标中可见。否则都是临时补丁,临时补丁应在您的配置审查或事件记录中标记出来。