nginx rewrite break 之后的 set 指令永远不会执行,变量为空导致 500
某天给一个反向代理层加了「变量化 proxy_pass」——为了让 nginx 每次请求都重新解析上游容器的 IP,避免容器重建后 IP 变了却被 nginx 缓存住(经典的 Docker 容器漂移 502 问题)。改动看起来很直白:加一个 resolver,把 proxy_pass 里的硬编码地址换成 $upstream_var,再用 set 给变量赋值。
上线后上游服务完全正常,直接 curl 端口返回 200,但通过 nginx 代理访问全部 500。日志里看到的是 nginx 自己的默认错误页,连上游都没打到。
现象
- 所有请求返回 HTTP 500,响应体仅 170 字节,
Server: nginx(不是上游返回的,是 nginx 自己生成的) - access log 里
rt=0.000,uct="-",urt="-"——nginx 根本没有向上游发起连接 - 直接
curl上游地址返回 200,说明上游完全健康 - error log 出现两行冒烟枪:
[warn] using uninitialized "upstream_var" variable
[error] invalid URL prefix in ""
配置大概长这样:
location /oss/ {
rewrite ^/oss/(.*) /bucket/$1 break; # ← break 在这里
set $upstream_var "http://storage:9000"; # ← 永远不会执行
proxy_pass $upstream_var; # → proxy_pass "" → 500
}根因
set、rewrite、if、return、break 这几个指令全都属于 ngx_http_rewrite_module。nginx 在 rewrite 阶段把这个 location 块里所有 rewrite 模块指令当成一段有序脚本执行。rewrite ... break 会就地终止这段脚本,后续的 set 虽然写在源码里,但永远不会被执行到。
proxy_pass 属于 ngx_http_proxy_module,在内容阶段(content phase)运行,比 rewrite 阶段晚。它确实能「看到」变量,但此时变量的值是空字符串 ""——因为赋值的 set 从未执行。
rewrite ... break 终止的是整个 rewrite 模块脚本,不只是 rewrite 本身这一行。凡是写在 rewrite ... break 后面的 set,一律不执行——即使语义上觉得「我只是在前面做了路径重写,变量赋值理应在后面声明」。
裸 break;(不带 rewrite 路径)也有完全相同的效果。
正解
把所有 set 移到 rewrite ... break 上面:
location /oss/ {
set $upstream_var "http://storage:9000"; # 先赋值,变量已填充
rewrite ^/oss/(.*) /bucket/$1 break; # break 在后,没问题
proxy_pass $upstream_var; # → proxy_pass "http://storage:9000" → OK
}proxy_pass 在内容阶段执行,此时 rewrite 阶段已经结束,变量已经被 set 填充好,一切正常。
只要把 set 放到 rewrite ... break 前面,变量就能在 rewrite 阶段被正常赋值,后续 proxy_pass $var 拿到的是真实地址而不是空字符串。
排查
这个坑最迷惑的地方是500 的外表像上游问题,但两个关键信号可以快速排除上游:
Server: nginx+ 170 字节定长响应体 = nginx 自己生成的错误,不是上游透传的- access log 里
urt="-"= nginx 从未向上游建立连接
确认是 nginx 本身的问题后,再看 error log 里的 uninitialized variable 警告,即可定位到变量未被赋值,顺藤摸瓜找到 set 写在 break 后面这个问题。
一句话外卖
nginx rewrite 模块是一段脚本,
break就是return——写在它后面的set是死代码,proxy_pass拿到的是空字符串,必然 500。