最稳妥的ua拦截方案是在nginx http块顶层用map定义$blocked_ua变量匹配可疑ua,再于location中通过if($blocked_ua)返回403,兼顾性能、安全与可维护性。

直接在 Nginx 配置中拒绝恶意 User-Agent,最安全、高效且可维护的方式是 用 map 指令定义匹配逻辑,再在 location 中判断并返回 403。这不是“可选方案”,而是唯一推荐的生产级做法。
必须把 map 放在 http 块顶层
`map` 必须写在 `http { ... }` 最外层,所有 `server` 块之前。它在 Nginx 启动时就完成正则预编译,运行时查表是 O(1) 时间复杂度,零额外开销。
错误写法:放在 `server` 或 `location` 里 —— 会报错或被忽略。
正确示例:
http {
map $http_user_agent $blocked_ua {
default 0;
~* ^$ 1; # 空 UA
~* ^[[:space:]]*$ 1; # 全空白 UA
~* (sqlmap|nikto|nmap|masscan|ZmEu|whatweb|acunetix) 1;
~* (python-requests|curl|wget|httpie|Go-http-client) 1;
~* (scanner|crawler|exploit|pentest|vuln|hack) 1;
~* Sogou web spider 0; # 白名单,放行(顺序重要!)
~* Baiduspider 0; # 同样放行
}
<pre class="brush:php;toolbar:false;">server {
listen 80;
server_name example.com;
location / {
if ($blocked_ua) {
return 403 "Access denied.";
}
try_files $uri $uri/ =404;
}
}}
为什么不能直接在 location 里写 if + 正则
虽然语法上允许,但存在严重隐患:
- 每次请求都重新执行正则匹配,性能随并发上升而明显下降
- 若后续有 `proxy_pass`、`fastcgi_pass` 或 `try_files`,`if` 可能被跳过或行为异常
- Nginx 官方明确标注 “if is evil”,尤其在非 `location` 内使用时
- 多个 `if` 并列会导致嵌套逻辑混乱,难以调试和审计
如何避免误伤正常用户
关键在规则设计和测试验证:
- 白名单规则必须写在黑名单之后,`map` 按顺序匹配,首个命中即终止
- 正则末尾加 `$` 锚点(如 `python-requests/.*$`),防止 `python-requests/2.28.1` 被漏掉,也防 `mypython-requests-tool` 被误杀
- 空 UA 和仅含空格/制表符的 UA 必须显式处理,否则默认走 `default 0` 被放行
- 用 `curl -H "User-Agent: sqlmap/1.7" http://localhost/` 实测,确认返回 403;再用 `curl -H "User-Agent: Mozilla/5.0" ...` 确认正常访问不受影响
增强防护与可观测性
单纯封禁只是第一步,建议同步配置:
- 在 `log_format` 中加入 `$blocked_ua` 字段,便于日志分析拦截效果
- 对高频触发 `$blocked_ua = 1` 的 IP 加限流,例如:
limit_req zone=ua_block burst=3 nodelay;(需提前定义 `limit_req_zone $binary_remote_addr zone=ua_block:10m rate=1r/s;`) - 针对敏感路径(如 `/admin`、`/wp-login.php`),可叠加 `location ^~ /admin` 单独强化规则,不依赖全局 UA 判断
- 每次修改后务必执行 `nginx -t` 校验语法,再 `nginx -s reload` 生效











