发布时间:2026-09-25 12:31 更新时间:2026-09-25 12:31 阅读量:0
很多站点的 Nginx 配置里,随着业务变复杂,会堆出一串 if 判断:判断 UA 是不是爬虫、判断 Referer 是不是外站、判断域名是不是主站,然后分别 return 或 rewrite。这种写法能跑,但问题也不少:if 在 location 外只能写 return/rewrite 一类指令,嵌套多了可读性差,改动一处容易连带影响另一处,日志里还看不出到底命中哪条规则。Nginx 的 map 指令正好用来解决这类「按条件取值」的场景——它把判断逻辑从 if 里抽出来,变成一张映射表,配置更直观,也更容易排错。
map 只能写在 http 块里,作用是:读取一个源变量(比如 $http_user_agent、$http_referer、$host),按你写的规则把结果赋值给一个新变量。之后在 server 或 location 里直接用这个新变量即可,不用再反复写 if。
它的匹配规则有几条要记牢:字符串匹配默认区分大小写,加 ~ 表示区分大小写的正则,加 ~* 表示不区分大小写;多个规则按配置中出现的先后顺序匹配,命中即停;都没命中时取 default 的值;如果连 default 都没写,结果就是空字符串。示例:
http {
map $http_user_agent $is_bad_ua {
default 0;
"~*curl" 1;
"~*python" 1;
"~*scrapy" 1;
"~*Go-http" 1;
}
}
这里 $is_bad_ua 只会有 0 或 1 两种结果。注意变量名不要和 Nginx 内置变量重名,也不建议用中文。map 的源变量既可以是内置变量,也可以是你之前用 map 得到的自定义变量,串起来用能表达更复杂的逻辑。
先说按 UA 做最基础的拦截。注意这里的思路是「拦截明显异常的采集特征」,而不是做安全防护的全部,不能指望它挡住所有自动化请求。配合 limit_req 之类模块使用效果更稳,具体参数以官方文档为准。
http {
map $http_user_agent $ua_block {
default 0;
"" 1; # 空 UA,常见于脚本
"~*scrapy" 1;
"~*python-requests" 1;
"~*Go-http-client" 1;
}
server {
listen 80;
server_name www.example.com;
if ($ua_block) {
return 403;
}
}
}
再说 Referer 防盗链。传统写法是在 location 里写 valid_referers,但如果你希望「放行搜索引擎、拦截外站图片引用、同时给一个友好的空 Referer 处理」,map 能写得更清楚:
http {
map $http_referer $ref_status {
default 0;
"" 1; # 直接访问,无 Referer
"~*example\.com" 0;
"~*baidu\.com" 0;
"~*bing\.com" 0;
"~*google\.com" 0;
}
server {
listen 80;
server_name www.example.com;
root /www/wwwroot/example;
location ~* \.(jpg|jpeg|png|gif|webp)$ {
if ($ref_status = 1) {
return 403;
}
expires 30d;
}
}
}
这段配置的含义是:外站带 Referer 引用图片时 $ref_status 为 0,正常放行;而完全没有 Referer 的请求(可能是用户直接粘贴地址,也可能是采集)返回 403。如果你希望空 Referer 也放行,把 "" 对应的值改成 0 即可。这里正则里的点号写成 \. 才是字面意义上的点,否则会匹配任意字符,这点很容易写错。
第三个场景是按域名做分流。同一台服务器上跑了主站和测试站,想让测试站只对特定来源开放,或者把某个域名整体重定向到新地址,都可以用 map 先算出一个目标值,再统一处理:
http {
map $host $site_target {
default "https://www.example.com";
"old.example.com" "https://www.example.com";
"test.example.com" ""; # 空值表示不做跳转
}
server {
listen 80;
server_name old.example.com test.example.com;
if ($site_target) {
return 301 $site_target$request_uri;
}
}
}
这里 $site_target 为空时不触发跳转,等于让 test.example.com 继续走本 server 的默认处理。把「判断」和「动作」分开,是 map 最大的价值:判断集中在一处,动作可以复用。
第一,map 必须放在 http 块,不能放进 server 或 location,写错位置 Nginx 会直接启动失败。第二,规则顺序决定结果,把更具体的规则写在前面,宽泛的 default 永远放最后。第三,$http_referer 可能为空也可能带端口,正则别写得太死。第四,if 在 Nginx 里是「邪恶的」,能用 map 表达的条件就不要用 if,尤其不要在 location 里用 if 去嵌套 rewrite。
改完配置后,先做语法检查再平滑重载,别直接 restart:
nginx -t
输出 syntax is ok / test is successful 后再执行
nginx -s reload
验证时可以用 curl 带不同 UA 和 Referer 直接打源站,观察返回码是否符合预期:
curl -I -A "python-requests/2.31" http://127.0.0.1/
curl -I -e "https://other-site.com/a.html" http://127.0.0.1/logo.png
curl -I -H "Host: old.example.com" http://127.0.0.1/
如果结果和预期不一致,先确认变量到底取到了什么值:可以把 $ua_block、$ref_status 这类变量加到 log_format 里,临时写进访问日志观察,比反复猜要快得多。修改日志格式同样需要 reload。
小结一下:map 适合处理「一个源变量映射到一个结果」的判断,UA 拦截、Referer 防盗链、按域名分流都是它的典型用法。配置时把握好三点——放在 http 块、注意规则顺序、给 default 一个明确值;改完先 nginx -t 再 reload,并用 curl 验证。下一步可以把这个思路延伸到按 IP 段限速、按请求方法限制接口等场景,让 Nginx 配置从一堆 if 变成一张清晰的映射表。
| 📑 | 📅 |
|---|---|
| 网站根目录被写入异常PHP文件:时间线、属主与日志反查上传入口 | 2026-09-25 |
| WordPress定时任务被wp-cron拖慢:改用系统crontab配置与验证 | 2026-09-25 |
| 宝塔面板网站备份还原到另一台服务器:跨机迁移的完整流程 | 2026-09-25 |
| WordPress文章ID与固定链接优化:伪静态改动后旧链接兼容 | 2026-09-25 |
| MySQL只允许本机连接后网站报错:bind-address与用户host授权取舍 | 2026-09-25 |
| CDN与源站双重缓存导致改版不生效:三层缓存刷新顺序 | 2026-09-26 |
| WordPress中文名附件变乱码或404:编码链路排查 | 2026-09-26 |
| 宝塔面板升级后网站打不开:PHP扩展、Nginx配置与面板服务逐项回滚排查 | 2026-09-26 |
| 域名下PC站与m站SEO冲突:canonical、自适应与UA跳转取舍 | 2026-09-26 |
| WordPress后台被暴力撞库告警:登录限速与日志加固 | 2026-09-26 |