Nginx 上游节点健康检查实战:max_fails、fail_timeout 与 backup 配置

    发布时间:2026-09-26 04:30 更新时间:2026-09-26 04:30 阅读量:0

    做反向代理的站长大多踩过这样的场景:后端三台应用服务器,其中一台挂掉或卡住,Nginx 却还按轮询往上打请求,用户端一片 502,整站体验跟着被拖垮。Nginx 本身没有类似 LVS 那种独立的健康检查进程,它靠的是被动健康检查——由真实请求的失败结果来判定节点好坏。理解这套机制,才能把 max_fails、fail_timeout 和 backup 节点配得合理。

    下面先看一个最小可用的 upstream 配置,后面所有讨论都围绕它展开。

    upstream app_backend {
        server 10.0.0.11:8080 max_fails=3 fail_timeout=10s;
        server 10.0.0.12:8080 max_fails=3 fail_timeout=10s;
        server 10.0.0.13:8080 max_fails=3 fail_timeout=10s backup;
    }
    
    server {
        listen 80;
        location / {
            proxy_pass http://app_backend;
            proxy_next_upstream error timeout http_502 http_503 http_504;
            proxy_connect_timeout 2s;
            proxy_read_timeout 10s;
        }
    }

    被动摘除是怎么发生的:失败计数与时间窗

    当 Nginx 通过 proxy_pass 转发请求到某个 server 时,如果连接失败、超时或返回了被 proxy_next_upstream 认定为失败的响应,该节点就会被记一次失败。默认情况下,在 fail_timeout 这个时间窗口内失败次数达到 max_fails,节点就被标记为不可用,并且在接下来的 fail_timeout 时长内不再被选中。

    这里有两个容易误解的点。第一,max_fails 不是永久计数,它统计的是 fail_timeout 窗口内的失败次数,窗口一过计数就重新开始。第二,fail_timeout 身兼两职:既是统计窗口长度,也是摘除时长。默认值分别是 max_fails=1、fail_timeout=10s,意味着只要一次失败就摘除 10 秒,对偶发抖动比较敏感,所以生产里常放宽到 3 次 / 10 秒到 30 秒,具体取值要看后端接口的容忍度。

    另外,只有被 Nginx 判定为失败的请求才计数。如果后端返回 500,但你没把 http_500 写进 proxy_next_upstream,Nginx 会把它当作正常响应直接发给客户端,节点不会被摘除。所以想摘除彻底,proxy_next_upstream 里要包含业务上真正代表不可用的状态码。需要说明的是,Nginx 开源版没有主动探测能力,如果后端是「端口在监听但业务已死」这种状态,被动检测只能等真实请求失败后才知道,这也是它和商业版 health_check 模块的主要区别。

    请求被摘除后去哪了:重试与 backup 节点

    节点被标记不可用后,Nginx 在后续 fail_timeout 内不会再把请求分给它。此时如果还有其它可用节点,请求会按权重轮询到剩余节点上;如果全部节点都不可用,Nginx 会返回 502 给客户端。

    重试行为由 proxy_next_upstream 控制。默认它包含 error 和 timeout,即连接错误和超时才换下一个节点。要注意重试次数不是无限的,Nginx 默认最多尝试 upstream 中的节点数那么多台,而且只有「还没向客户端发送任何响应数据」的请求才能安全重试。如果后端已经开始返回响应体才中断,Nginx 不会贸然重试,避免产生重复提交。

    backup 节点平时不参与负载,只有当主节点全部被标记不可用时才会接管流量。它适合放一台低配兜底机器,或者用来承接降级页面。需要注意的是,backup 节点同样遵守 max_fails 和 fail_timeout,如果它自己也不健康,兜底就失效了,所以别把 backup 当成万能保险。

    另外,如果后端是有状态服务,重试可能造成重复写入。这种情况下可以在 proxy_next_upstream 里去掉非幂等请求的重试,比如只保留 error timeout,并让业务层自己处理幂等。

    怎么验证配置真的生效

    改完配置先做语法检查再平滑重载:

    nginx -t
    nginx -s reload
    

    验证节点摘除效果,可以临时把某台后端停掉,然后用循环请求观察错误率与响应来源。下面这条命令会持续请求并打印状态码,方便观察摘除前后差异:

    for i in $(seq 1 20); do
      curl -s -o /dev/null -w "%{http_code} %{time_total}\n" http://127.0.0.1/
      sleep 0.5
    done
    

    同时看 error.log 里有没有 upstream 连接失败的记录,确认失败计数确实在累积。如果日志中反复出现同一节点失败却始终没被摘除,重点检查三件事:proxy_next_upstream 是否覆盖了实际返回的状态码、max_fails 是否被设得过大、以及失败请求是否恰好分散在多个 fail_timeout 窗口内没有凑够次数。

    调优时还有几个实践建议:把 proxy_connect_timeout 设小一点(比如 2 秒),让连不上的节点更快被判定失败;proxy_read_timeout 按接口最长耗时设置,避免慢请求把节点拖到超时。摘除时长不要设得太短,否则故障节点在恢复前反复被探测,容易造成流量抖动;也不要设得太长,否则节点恢复后长时间收不到流量。10 秒到 30 秒是常见区间,最终以实际业务表现和官方文档为准。

    小结一下:Nginx 的被动健康检查本质是「用真实请求的失败换节点状态」,max_fails 和 fail_timeout 决定摘除的灵敏度与时长,proxy_next_upstream 决定哪些失败算失败、请求能否换节点,backup 只在主节点全挂时兜底。把这几个参数配合好,单台后端故障就不会再拖垮整站。下一步建议结合监控观察上游错误率曲线,再反过来微调这几个阈值。

    继续阅读

    📑 📅
    MySQL主从复制中断后如何安全恢复 2026-09-25
    宿主机与容器时间不一致导致接口签名失败排查 2026-09-25
    Nginx与上游服务时间不同步导致签名校验失败排查 2026-09-25
    内核日志里的 OOM 线索:oom_score、cgroup 限制与判断内存不足原因 2026-09-25
    Linux 服务器 OOM Killer 日志怎么看:dmesg 与 journalctl 定位被杀进程 2026-09-25
    MySQL死锁日志定位实战:读懂SHOW ENGINE INNODB STATUS 2026-09-26
    Linux服务器内存泄漏初判:RSS上涨是缓存还是泄漏 2026-09-26
    Docker 挂载 NFS 卷卡死排查:stat、df 与 nfsstat 定位失联 2026-09-26
    PostgreSQL WAL 堆积与复制槽不释放排查实战 2026-09-26
    Nginx proxy_pass 带 URI 与不带 URI:路径拼接错乱排查 2026-09-27