Nginx 代理超时与故障转移:用 proxy_next_upstream 和 proxy_connect_timeout 拯救挂掉的后端

后端服务器宕机时,Nginx 默认会等待超时返回 504,客户端体验极差。本文从零讲解 proxy_connect_timeout 和 proxy_next_upstream 的原理与搭配策略,让 Nginx 自动跳过坏的节点,把请求转发给健康的后端,实现真正的故障转移与高可用。

Nginx 代理超时与故障转移:用 proxy_next_upstream 和 proxy_connect_timeout 拯救挂掉的后端
封面图:ZuCDN · ZuCDN 原创

如果你正在处理Nginx,先别急着照搬网上的参数。后端服务器偶尔会挂——数据库连接池耗尽、进程 OOM、网络瞬间抖动,这些情况在你的生产环境里迟早出现。问题的关键不在“会不会挂”,而在于 Nginx 在服务器挂了之后会干什么。

默认配置下,Nginx 会耐心等待 proxy_connect_timeout 规定的秒数,如果连不上,再返回 504。一个 5 秒的超时,乘以 10 个请求,用户就要干等半分钟。这是最差的体验。真相是:Nginx 可以快速发现故障并立刻把请求转给其他后端,不需要让用户看到 504 或等待。

做到这件事只需要两个指令:proxy_connect_timeoutproxy_next_upstream。下面从概念讲到调优,覆盖新手最容易搞混的地方。

代理超时:连接、读取、发送分别控制什么?

验证与回滚

Nginx 作为反向代理时,与上游服务器(后端)的通信分三个阶段:

  • 连接阶段:Nginx 尝试与上游 IP:Port 建立 TCP 连接。控制指令是 proxy_connect_timeout,默认 60 秒。
  • 发送请求阶段:连接建立后,Nginx 把从客户端收到的请求数据转发给上游。超时用 proxy_send_timeout,默认也是 60 秒。
  • 接收响应阶段:上游开始返回数据,但如果两次读操作间隔过长,会触发 proxy_read_timeout,默认 60 秒。

故障转移关心的是第一阶段——连接超时。如果上游 Linux 内核已经半连接或端口被防火墙阻断,握手会卡住很久。把 proxy_connect_timeout 从 60 秒降到 3~5 秒,可以让 Nginx 快速失败,而不是吊死在一棵树上。

proxy_next_upstream:故障转移的核心开关

故障定位思路

这个指令告诉 Nginx:“当本次与上游通信出现某种问题时,要不要换下一个上游节点重试?”它接受多个参数,常用如下:

  • error —— 与上游建立连接、发送请求或读取响应时发生错误(如连接被拒绝、超时、未收到完整响应等)
  • timeout —— 在 proxy_connect/read/send_timeout 规定时间内未完成操作
  • invalid_header —— 上游返回的响应头格式错误或为空
  • http_500 / http_502 / http_503 / http_504 —— 上游返回了指定的 HTTP 状态码
  • non_idempotent —— 默认情况下,Nginx 不会重试 POST、PATCH、DELETE 等非幂等请求,加上这个参数才会重试(但风险很大,慎用)

默认行为是:proxy_next_upstream error timeout。也就是说,Nginx 只会在连接出错或超时时转移请求,而不会因为上游返回 500 就重试。很多新手以为 Nginx 会自动跳过返回 500 的节点,其实需要主动加 http_500

Nginx:把超时时间和重试策略搭配起来

容易忽略的细节

只看单条指令是不够的,关键是组合后的效果。

假设 proxy_connect_timeout 5s,但没有开启 proxy_next_upstream timeout(或者只开了 error),那么上游连不上时 Nginx 会在 5 秒后报错,但不会换下一个节点,最终返回 504 给用户。

反过来,如果只配了 proxy_next_upstream error timeout,但 proxy_connect_timeout 还是 60 秒,用户要等一分钟才触发转移,等于没优化。

推荐配置组合(入门级)

upstream backend {
    server 10.0.0.1:8080 max_fails=2 fail_timeout=10s;
    server 10.0.0.2:8080 max_fails=2 fail_timeout=10s;
}

server {
    location / {
        proxy_pass http://backend;
        proxy_connect_timeout 3s;
        proxy_send_timeout 10s;
        proxy_read_timeout 30s;
        proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
        proxy_next_upstream_tries 2;
    }
}

解释每一条:

  • proxy_connect_timeout 3s:连接超时设为 3 秒,比默认快 20 倍。
  • proxy_next_upstream ... error timeout invalid_header http_500 http_502 http_503:除了错误和超时,如果上游返回 5xx,也立即尝试下一个节点。
  • proxy_next_upstream_tries 2:最多尝试 2 个节点(包含第一次)。如果设成 3,Nginx 会尝试所有可用节点。

注意 upstream 块里的 max_failsfail_timeout 是 Nginx 自带的被动健康检查机制。它和 proxy_next_upstream 是两层逻辑:proxy_next_upstream 决定是否在当前请求中换节点;max_fails 决定一段时间内累计失败多少次后把节点标记为 down,后续请求不再发给它。它们协同工作,前者保证单次请求的快速转移,后者保证长期持续的故障不会不断重试。

常见误区与风险

非幂等请求不要重试

默认 Nginx 不会重试 POST 等非幂等请求。如果你强行加 non_idempotent,可能会重复扣款、重复创建订单。如果你确实需要重试(比如接口本身就是幂等的),才可以用,但一定要确认。

http_504 要不要加?

如果上游本身正常但处理超时(例如请求一个慢 SQL),504 是来自 Nginx 自身(上游超时),而不是上游返回的。这种情况下 Nginx 不会重试,因为 Nginx 认为错误是自己产生的。所以 http_504 加了也没用,因为上游根本没返回 504。正确的做法是用 timeout 参数覆盖 read 超时的情况。

proxy_next_upstream_timeout 与 proxy_next_upstream_tries

Nginx 1.11.0 之后还有两个相关参数:proxy_next_upstream_timeoutproxy_next_upstream_tries。前者限制整个重试链的总耗时,后者限制总尝试次数。建议总时间不超过客户端超时的一半,例如客户端超时 30 秒,则 proxy_next_upstream_timeout 设为 15 秒。

验证配置是否生效

我的处理经验

改完配置文件后,reload 生效:nginx -t && systemctl reload nginx

然后模拟故障:

  • 临时停掉一台后端:systemctl stop your-app
  • curl -w "%{http_code} %{time_total}n" http://your-domain 观察返回状态码和总耗时。
  • 如果配置正确,你会看到请求在几十毫秒内成功返回(因为 Nginx 立即连接下一个后端),而不是等几秒后返回 504。

更进一步的验证可以用 abwrk 做压力测试,同时观察 Nginx 访问日志中上游地址是否在故障时发生了切换。

调优建议:先降超时,再加重试,再调状态码与Nginx

容易忽略的细节

如果你的业务可以接受较短的连接等待时间(通常局域网内后端 1-3 秒足够),可以按这个步骤逐步收紧:

  1. proxy_connect_timeout 从默认 60s 降至 5s;
  2. 确认 proxy_next_upstream 包含 error timeout
  3. 观察一周,如果没有误判(比如网络抖动造成的不必要转移),再降至 3s;
  4. 如果后端业务偶发 500,可以加入 http_500 http_502 http_503
  5. 如果后端是长耗时 API,考虑 proxy_next_upstream_timeout 限制总重试时间。

不要急于一步到位。过低的超时可能让正常的慢节点被反复跳过,导致所有流量集中到正常节点上,反而降低整体可用性。好在 Nginx 的重试策略是“有记忆的”(配合 max_fails),极端场景下即使配置激进,系统也会自动恢复。

最后,记得在 upstream 块中正确设置所有后端节点的权重和备份标志。配合 proxy_next_upstream,你的 Nginx 才能真正做到“后端挂了也不怕”。

延伸阅读