Nginx if指令安全使用指南
Nginx 的 if
指令是“生产环境高危区”,我们内部叫它 “if 地狱”(if hell)。但不用又不行,关键是怎么安全地用。
下面拆解:
- 哪些
if
能用(安全场景) - 哪些
if
绝对禁用(踩坑重灾区) - 5 个真实生产案例(含配置 + 验证命令)
- 替代方案(用
map
/location
避开if
)
一、先说结论:Nginx if
的“生死线”
官方警告(Nginx Pitfalls):
“if is evil” —— 但它不是不能用,而是不能乱用。
安全使用 if
的 唯一前提:
if
必须在location
块内,且只用于以下 3 种判断:
- 检查变量(如
$request_method
,$http_user_agent
)- 返回响应(
return
,rewrite ... last
)- 设置变量(
set
)
绝对禁止的 if
用法:
- 在
server
块外用if
- 在
if
里用proxy_pass
/fastcgi_pass
(会导致 worker crash) - 多层嵌套
if
二、5 个生产级 if
实例(附验证命令)
实例 1:禁止非 GET/POST 请求(防扫描)
location / {# 只允许 GET/POSTif ($request_method !~ ^(GET|POST)$ ) {return 405;}try_files $uri $uri/ /index.php;
}
为什么安全?
- 在
location
内- 只用
return
- 无副作用
验证命令:
curl -X DELETE http://yoursite.com/ # 应返回 405
实例 2:屏蔽恶意 User-Agent(防爬虫)
location / {if ($http_user_agent ~* "python-requests|Scrapy|HttpClient") {return 403;}proxy_pass http://backend;
}
安全点:
- 正则匹配 UA
- 直接
return 403
,不转发
注意:
不要用$http_user_agent = "xxx"
(精确匹配易绕过),必须用正则。
验证命令:
curl -A "python-requests/2.25.1" http://yoursite.com/ # 应 403
实例 3:强制跳转带 www 域名(安全版)
server {listen 80;server_name example.com www.example.com;# 错误写法(在 server 块用 if):# if ($host = "example.com") { ... }# 正确写法:用两个 server 块(推荐)
}server {listen 80;server_name example.com;return 301 https://www.example.com$request_uri;
}server {listen 80;server_name www.example.com;# 正常业务
}
结论:域名跳转不要用
if
,用多server
块更安全、性能更高。
实例 4:根据参数灰度(安全用法)
location /api/ {# 灰度:带 gray=1 的请求走测试环境if ($args ~ "gray=1") {set $backend test_backend;}if ($args !~ "gray=1") {set $backend prod_backend;}proxy_pass http://$backend;
}
风险提示:
虽然用了set
,但proxy_pass
在if
外,所以安全。
绝对不要写成:if ($args ~ "gray=1") {proxy_pass http://test; # ❌ 危险!可能导致 worker 异常 }
验证命令:
curl "http://yoursite.com/api/user?gray=1" # 应走 test curl "http://yoursite.com/api/user" # 应走 prod
实例 5:防盗链(Referer 检查)
location ~* \.(jpg|png|gif)$ {# 允许空 Referer(直接访问)和本站if ($http_referer !~* "^https?://(www\.)?example\.com") {if ($http_referer != "") {return 403;}}
}
安全点:
- 在
location
内- 只用
return
- 双重
if
但无副作用
验证命令:
curl -e "https://evil.com" http://yoursite.com/test.jpg # 应 403 curl http://yoursite.com/test.jpg # 应 200
三、if
的替代方案(更安全、高性能)
替代 1:用 map
(推荐!)
场景:根据 UA 返回不同内容
# 在 http 块定义
map $http_user_agent $is_mobile {default 0;"~*mobile" 1;"~*android" 1;"~*iphone" 1;
}server {location / {if ($is_mobile) {rewrite ^ /mobile/index.html last;}# ...}
}
优势:
map
在启动时预计算,性能比if
高 10 倍。
替代 2:用多 location
块
场景:API 版本路由
# 用 if(不推荐)
# if ($uri ~ "^/api/v1") { ... }# 用 location(推荐)
location /api/v1/ {proxy_pass http://v1_backend;
}location /api/v2/ {proxy_pass http://v2_backend;
}
优势:无
if
,配置清晰,性能最优。
四、 if
使用规范
- 禁止在
server
块使用if
(除return
外); if
内禁止出现:proxy_pass
,fastcgi_pass
,uwsgi_pass
,scgi_pass
;- 复杂逻辑必须用
map
+location
实现; - 所有
if
规则必须有单元测试(用curl
+grep
验证)。
五、调试 if
问题的命令
# 1. 检查配置语法
nginx -t# 2. 查看变量值(调试神器)
location /debug {add_header X-Method $request_method;add_header X-UA $http_user_agent;add_header X-Args $args;return 200 "debug\n";
}# 3. 模拟请求验证
curl -H "User-Agent: python-requests" -I http://yoursite.com/
“能不用 if,就不用 if;必须用 if,就只用 return/set。”
记住:90% 的 Nginx 崩溃,都是因为if
里写了proxy_pass
。
现在立刻去检查你的配置:
grep -r "if.*proxy_pass" /etc/nginx/
如果输出非空——马上改! 别等半夜报警!