返回博客

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.000uct="-"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
}

根因

setrewriteifreturnbreak 这几个指令全都属于 ngx_http_rewrite_module。nginx 在 rewrite 阶段把这个 location 块里所有 rewrite 模块指令当成一段有序脚本执行。rewrite ... break就地终止这段脚本,后续的 set 虽然写在源码里,但永远不会被执行到。

proxy_pass 属于 ngx_http_proxy_module,在内容阶段(content phase)运行,比 rewrite 阶段晚。它确实能「看到」变量,但此时变量的值是空字符串 ""——因为赋值的 set 从未执行。

坑:rewrite break 之后的 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 之前声明

只要把 set 放到 rewrite ... break 前面,变量就能在 rewrite 阶段被正常赋值,后续 proxy_pass $var 拿到的是真实地址而不是空字符串。

排查

这个坑最迷惑的地方是500 的外表像上游问题,但两个关键信号可以快速排除上游:

  1. Server: nginx + 170 字节定长响应体 = nginx 自己生成的错误,不是上游透传的
  2. access log 里 urt="-" = nginx 从未向上游建立连接

确认是 nginx 本身的问题后,再看 error log 里的 uninitialized variable 警告,即可定位到变量未被赋值,顺藤摸瓜找到 set 写在 break 后面这个问题。

一句话外卖

nginx rewrite 模块是一段脚本,break 就是 return——写在它后面的 set 是死代码,proxy_pass 拿到的是空字符串,必然 500。