
如何用 Nginx upstream 把多个后端服务组成一个大池子,让请求自动分到不同节点、并在某个节点故障时自动避开?这是很多站点从单机走向集群时遇到的第一个门槛:后端一旦多于一台,光靠一个 server 段就撑不住了,需要一套既能转发、又能调度、还能剔除故障节点的配置。本文会从 upstream 的基础语法讲起,带你逐个看轮询、权重、IP Hash 这几类负载均衡策略,再重点说明主动与被动健康检查的区别和取舍。为了避免改完配置却不知道有没有生效,全文每一节都会给出可执行的验证命令,帮你把配置落在真实环境里而不是停在纸面上。
负载均衡(Load Balancing,把流量分配到多台后端服务器的机制)并不是 Nginx 独有的概念,但 Nginx 凭借轻量的反向代理能力和灵活的 upstream 模块,成为搭建后端池最常用的入口之一。如果你还不太清楚反向代理在整套架构里的位置,可以先翻看这篇Nginx 反向代理与负载均衡指南,把“谁来接入流量、谁来转给后端”的关系理顺,再回来配置会更省力。
先搭一个最小的 upstream 后端池
upstream 块通常放在 Nginx 的 http 层级,作用是先声明一组后端地址,再由 server 段里的 proxy_pass 引用。最小配置长这样:
http {
upstream backend_pool {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
server 10.0.0.13:8080;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://backend_pool;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
这里 upstream backend_pool 声明了三台后端,proxy_pass 直接指向池子的名字,请求就会按策略分到其中一台。加载前先用 nginx -t 检查语法,再 nginx -s reload 重载,不要直接重启生产 Nginx。验证是否生效,可以用 curl -I http://app.example.com 连续请求几次,配合后端日志里记录的来源 IP 或自定义响应头来判断流量是否真的被分到了多台机器。
配置里有两个字段要顺手养成习惯:proxy_set_header Host 保证后端拿到的是原始域名而不是内网 IP,否则多站点共用一个后端池时容易串站;proxy_set_header X-Real-IP 把真实客户端 IP 传给后端,方便排查访问日志。这两个头是反向代理场景下的兜底项,漏掉一个,后面的登录态、限流和审计都可能出错。
选择负载均衡策略:轮询、权重、IP Hash 怎么挑
upstream 块不写任何策略时,Nginx 默认使用轮询(Round Robin),即请求按顺序轮流发给每个后端,适合后端性能相近、链路简单的场景。它实现最简单,但缺点也明显:如果某一台后端是旧机器、配置明显更低,轮询仍然会平分流量,可能把慢节点拖成瓶颈。
这时可以给节点加 weight 权重。下面的配置让配置较强的节点承担更多流量:
upstream backend_pool {
server 10.0.0.11:8080 weight=3;
server 10.0.0.12:8080 weight=2;
server 10.0.0.13:8080 weight=1;
}
权重值越大,被选中的概率越高,适合“多台机器规格不同、希望按算力分配”的场景。下面的示意图展示了请求流被按不同粗细分发到三台权重不同的后端节点:

还有一种常见策略是 IP Hash(ip_hash;),它会按客户端 IP 计算一个固定的分配结果,让同一个用户的请求尽量落到同一台后端。这对需要本地会话(Session)而没做集中式会话存储的应用很友好,但代价是负载分散相对固定,某个 IP 段的用户压力会集中到同一节点,扩容或缩容时也更容易出现偏移。
如果后端响应时间差异大、连接数不均衡明显,可以考虑 least_conn; 最小连接策略,把新请求分给当前活跃连接数最少的节点。选择哪种策略没有银弹:后端规格接近看重简单稳定就选轮询,机器性能差异大就加权重,应用强依赖本地会话又不想动代码就选 IP Hash,后端各节点负载波动剧烈则倾向最小连接。换策略只需改 upstream 块里的一个指令并 reload,成本很低,建议结合压测结果来定,而不是照抄网上模板。
健康检查:被动发现故障还是主动探测
负载均衡只解决“怎么分流量”,真正让后端池在高可用(High Availability,系统在部分组件故障时仍能持续对外服务的能力)上立住的是健康检查。Nginx 提供的方案分成两类:被动健康检查(Passive Health Check)和主动健康检查(Active Health Check)。两者的区别在于,被动方式只在请求经过时观察后端是否失败,主动方式则由 Nginx 自己定期发起探测请求,即使没有真实流量也能发现节点异常。

被动健康检查是 Nginx 开源版默认自带、无需额外编译模块的能力,用 max_fails 和 fail_timeout 就能控制。看这段配置:
upstream backend_pool {
server 10.0.0.11:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.12:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.13:8080 max_fails=3 fail_timeout=30s;
}
含义是:在 fail_timeout(30 秒)这个时间窗内,如果某台后端连续失败达到 max_fails(3)次,就把这台标记为不可用,并在接下来 30 秒内不再把请求转发给它,这段时间过后会自动重新探测。默认判断失败的依据是连接失败、超时以及 5xx 类错误。它的优势是零成本、开源版直接能用,缺点是必须有人访问才会触发判断——如果某个夜间完全没流量,故障节点可能一晚上都不会被剔除。
主动健康检查:让 Nginx 定期自己探测
如果业务对可用性要求更高,希望即使没有真实请求也能及时发现故障节点,就需要主动健康检查。开源版 Nginx 默认不带这个能力,需要安装官方商业版模块 ngx_http_upstream_module 的 health_check 功能,或者选用 Nginx 社区构建的 ngx_http_upstream_check_module 补丁版本。是否启用主动检查,关键看你的发行版是否内置对应模块——在 nginx -V 的编译参数里看到 --with-http_upstream_check_module 才算具备,否则即便写了配置也会在 nginx -t 时报 “unknown directive”。
以社区补丁模块为例,主动健康检查的配置长这样:
upstream backend_pool {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
server 10.0.0.13:8080;
check interval=5000 rise=2 fall=3 timeout=2000 type=http;
check_http_send "HEAD /health HTTP/1.0\r\nHost: app.example.com\r\n\r\n";
check_http_expect_alive http_2xx;
}
这段配置每 5 秒(interval=5000)向每台后端发送一次 HEAD /health 请求;连续成功 2 次(rise=2)判定节点恢复,连续失败 3 次(fall=3)判定节点故障并停止转发流量,单次探测超时为 2 秒。关键是后端要提供一个轻量的 /health 接口,它返回的 2xx 状态码才代表“业务真的可用”——只检查端口通是不够的,很多故障是进程活着但应用已卡死。
排障与验证:别让配置只停在“能加载”
写好 upstream 配置只是开始,真正的坑往往藏在验证环节。改完配置、通过 nginx -t 之后,先用下面几个动作确认池子真的在按预期工作:
第一,看实时连接分布。执行 ss -tn state established '( dport = :8080 )' 观察 Nginx 连到哪些后端的端口,再对比后端各自的访问日志是否都有新增请求。第二,主动模拟故障后测试剔除。把其中一台后端的应用停掉,用 curl -s -o /dev/null -w "%{http_code}\n" http://app.example.com 连续请求,观察失败是否先出现几次、随后被屏蔽、之后始终拿到 200。第三,确认被动检查的参数是否被真正加载——部分情况下 fail_timeout 与 proxy_next_upstream 的配合会让“失败重试”掩盖剔除行为,需要结合错误日志 upstream connect error 一起判断。
这里要特别提醒一个很容易踩的坑:Nginx 默认只对连接失败和超时做被动剔除,如果后端返回了 502/504 之类由 Nginx 自己产生的错误,通常不会触发节点下线。若想让应用层故障也能被识别,要么给后端加上前面说的主动健康检查接口,要么在业务代码里让异常直接断开连接或返回 4xx 之外的明确错误。如果这套反向代理和连接管理配置要和 Linux 内核的网络参数一起调优,建议先看这篇服务器网络参数 sysctl 实战,把系统层连接队列与 Nginx 的转发上限对齐,避免内核放行而 Nginx 又拦回去。
把后端池接进日常运维
upstream 后端池不是配完就一劳永逸。建议在交付前做一次完整演练:先在测试环境把某台后端停掉,确认流量在几十秒内自动切走、应用不出现长时间报错,再把节点恢复,观察它是否能顺利回池。演练通过后,把 Nginx 的访问日志和错误日志接到监控里,重点关注 upstream 相关的超时与重试条目。这样当某台后端真正出问题,你能在用户感知之前从日志或告警里看到征兆。
对运行在 [VPS](https://cn.hostease.com/vps/)([虚拟专用服务器](https://cn.hostease.com/vps/))上的中小站点来说,upstream 加被动健康检查基本够用;只有当站点流量大、业务对可用性有硬性指标,才值得为了主动检查去选用带对应模块的 Nginx 构建。你可以先从这里入手,用一台真实服务器把最小池子搭出来,再逐步叠加权重和健康检查,比一次写一大段配置更稳妥。入口层、Web 服务器与数据库的连接管理要一起规划,相关做法可以参考这篇Nginx worker 进程与连接数实战,把连接上限与 upstream 的并发模型对齐。如果你希望直接在一台配置好网络与入口层的 VPS(虚拟[专用服务器](https://cn.hostease.com/dedicated-server/))上做这类验证,Hostease 提供的VPS 主机方案自带独立 root 权限和完整网络配置,方便你自由实践本文的 upstream 与健康检查步骤。关于反向代理入口的更完整配置思路,也可以再参考我们的WordPress 优化专题,把入口层和应用层串起来排查。总的来说,推荐你按“先最小池子、再加权重、最后上健康检查”的顺序落地,每一步都跑一遍验证命令,遇到不确定的节点状态就回到 ss 与错误日志里找证据。