nginx中变量应在server块用map或set+if预定义,location内仅引用;避免在location中用if+set,因其惰性执行易致变量未赋值;map更安全高效。

在 Nginx 的 location 块中直接用 set 定义变量做逻辑判断,不推荐且容易出错。因为 set 本身不带条件能力,它只是赋值;而 if 在 location 中行为不稳定,组合使用风险高。真正安全、清晰的做法是:把变量定义和条件判断拆开,优先在 server 块中用 map 或 set + if 提前准备变量,再在 location 中引用。
✅ 正确做法:变量定义放在 server 块,location 只负责使用
set 必须“先定义、后使用”,且不能跨块可靠继承。location 不是 server 的子作用域,server 里 set 的变量默认不会自动传入 location(除非该 location 是嵌套在 if 内,但那更危险)。
推荐结构:
server {
# 统一提取并赋值,集中管理
set $app_env "prod";
if ($host ~ "^staging\.") {
set $app_env "staging";
}
if ($host ~ "^dev\.") {
set $app_env "dev";
}
location /api/ {
# 直接使用已定义的变量
proxy_pass http://backend_$app_env;
proxy_set_header X-Env $app_env;
}
location /static/ {
alias /var/www/$app_env/static/;
}
}
⚠️ 注意:上面写法仅在 server 级 if 中有效;若把 set 和 if 全挪进 location,会触发 Nginx 的“惰性执行”问题——变量可能未赋值就引用,导致空值或 404。
⚠️ location 块内慎用 if + set 的典型陷阱
以下写法看似合理,实则不可靠:
location /admin/ {
set $allow "false";
if ($remote_addr = "192.168.1.100") {
set $allow "true"; # 这个赋值不一定生效
}
if ($allow = "false") {
return 403;
}
}
问题在于:
-
if在location中不是原子执行,可能被跳过或重排序 -
$allow初始值为空字符串,不是"false",if ($allow = "false")实际判断的是"" = "false"→ 假 - 多层
if嵌套时,set赋值顺序和可见性难以保证
✅ 更健壮的替代方案:用 map 做无副作用映射
对简单条件(如按域名、参数、UA 切换后端),直接用 map 替代 if + set:
map $host $backend {
hostnames;
staging.example.com "http://10.0.2.10:8000";
dev.example.com "http://10.0.2.11:8000";
default "http://10.0.2.12:8000";
}
server {
location /api/ {
proxy_pass $backend$request_uri;
}
}
✅ 优势:配置加载时编译完成、零运行时开销、无作用域问题、支持正则和通配。
✅ 实用技巧:用 set 拼接动态路径或标记状态
当你需要拼接字符串(比如带版本号的静态路径),set 很有用,但必须确保前置逻辑已执行:
server {
# 提取版本参数,设默认值
set $version "v1";
if ($args ~* "v=([^&]+)") {
set $version "v$1";
}
location /assets/ {
alias /var/www/app/$version/assets/;
}
}
关键点:
-
set必须在location外(如server或同级if)先执行 - 所有
$version引用都依赖这个提前赋值 - 若
if不匹配,$version保持"v1",不会变为空
❌ 明确禁止的操作
- 在
http块顶层(即不在server内)写set→ 报错 - 在
log_format中引用set定义的变量 → 不支持,需改用map或内置变量 - 写
set $flag ""; if (...) { set $flag "1"; }然后在location外用$flag→ 值不确定,因if不一定执行
不复杂但容易忽略:变量必须显式初始化,且只对当前请求生命周期有效。











