发布时间: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 模块的主要区别。
节点被标记不可用后,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 |