least_conn 本身不会导致调度死循环,它仅从健康节点中选择活跃连接数最少的后端,不涉及重试或路径改写;所谓“死循环”实为后端重定向、多层代理错配或健康检查与连接复用失当所致。

least_conn 本身不会导致调度死循环——它只是从健康节点里挑一个当前活跃连接数最少的后端,不涉及重试、跳转或路径改写。所谓“least_conn 调度死循环”,实际是误判:真正引发循环的是 后端响应行为、多层代理配置冲突 或 健康检查与连接复用配合失当 所致。
先确认是不是真有循环
别急着改 upstream,先验证是否真的在 Nginx 层面反复转发:
- 查 access_log,加
$upstream_addr $upstream_status $request_time字段,看同一请求是否多次出现在日志中、且$upstream_addr在多个后端间来回跳 - 用
curl -v http://your-domain/xxx观察响应头里的Location和状态码,确认是否有 301/302 连续跳转 - 抓包(如
tcpdump -i any port 80 or port 443)比对客户端发起一次请求后,Nginx 是否向后端发了不止一次
重点排查三类真实循环源头
1. 后端主动重定向形成闭环
典型场景:SPA 前端 history 模式 + 后端未正确 fallback;或鉴权中间件检测到未登录,反复 302 到登录页,而登录页又触发同样逻辑。
- 直连后端(绕过 Nginx)测试:
curl -v http://backend-ip:8080/path,看是否也跳转 - 若直连正常,说明问题出在 Nginx 透传头缺失,补上:
proxy_set_header X-Forwarded-Proto $scheme;和proxy_set_header Host $host; - 若直连也跳,问题在后端,需检查路由配置、鉴权拦截链、或加
try_files $uri $uri/ /index.html;终止前端路由 fallback 循环
2. 多层 Nginx 代理因 Host 或路径错配打转
例如:Nginx A → Nginx B → Nginx A,尤其常见于 Nginx Proxy Manager + 自建 Nginx 或 Docker 网络中。
- 检查每层 proxy_pass 的 URL 尾部斜杠是否一致:location /api/ 对应 proxy_pass http://b/api/(带尾斜杠),否则可能拼出 /api/api/ 再被 location ~ ^/api/ 匹配,再次转发
- 禁用模糊匹配的正则 location,如
location ~ \.php$或location /无终止条件,可能劫持本该 proxy_pass 的请求 - 强制设置 Host 头:
proxy_set_header Host "backend-real-domain.com";,避免 DNS 解析回环
3. 健康检查 + least_conn 协同异常引发“假循环”感知
不是真循环,但表现像:请求不断落到同一台后端,连接数飙升,然后超时失败,再重试——用户看到延迟高、重试多,误以为在“反复调度”。
- 检查是否启用了
proxy_next_upstream error timeout http_500 http_502但没配max_fails,导致故障节点未被剔除,least_conn 持续选它 - 确认 keepalive 值是否过大(如
keepalive 200),后端又未及时关闭空闲连接,造成 Nginx 统计的“活跃连接数”虚高,least_conn 误判其他节点“更空闲”,结果流量集中过去 - 查看
nginx -T | grep -A5 "upstream",确认没有把least_conn和ip_hash或hash写在同一 upstream 块里——语法错误虽会报错,但部分旧版本可能静默忽略,导致策略失效
快速验证 least_conn 是否生效
别依赖日志猜,用实时指标交叉验证:
- 开启 Nginx stub_status(需编译时含 http_stub_status_module):在 server 块加
location /nginx_status { stub_status; allow 127.0.0.1; deny all; },访问后看 Active connections 分布 - 用
ss -tnp | grep :8080 | wc -l在各后端机器上统计 ESTAB 连接数,对比是否与$upstream_addr日志趋势一致 - 临时把两个后端的
max_conns设为极小值(如 2 和 3),再压测,看请求是否严格按上限被跳过——能跳过,说明 least_conn 逻辑通;不能跳,说明配置未加载或模块缺失


















