Nginx 通过 map 指令分层识别 UA 类型(scanner/harvester/invalid/allowed),server 块顶层统一拦截并标记状态,扩展日志格式记录 $ua_type 与原始 UA,结合限流与统计优化规则,实现精准拦截、可审计、防误伤。

直接在 Nginx 的 server 块顶层定义 UA 画像规则,配合日志格式扩展与状态码标记,就能实现精准拦截+可审计记录。关键不是堆正则,而是分层识别、留痕清晰、避免误伤。
构建可维护的 UA 画像规则
用 map 指令建立语义化分类,比一堆 if 更高效稳定:
- 区分类型:把扫描器(
sqlmap、nuclei)、采集型 Bot(bytespider、semrush)、异常 UA(空值、纯空白、短横线)分别打标,例如$ua_type值设为scanner/harvester/invalid - 统一小写处理:在
map中用~*匹配,确保Baiduspider和baiduspider都能捕获 - 排除白名单:对已知合法监控工具(如
Prometheus/Alertmanager)、CI/CD 请求(GitLab-Runner)显式设为0或allowed,防止误封
在 server 块中触发拦截并标记状态
不要在 location 内写 if,放在 server { ... } 最上方:
- 用
if ($ua_type != "allowed") { return 403; }统一拦截,避免重复判断 - 返回前加日志标记:通过
log_by_lua_block(若启用 OpenResty)或自定义变量,在 access_log 中注入拦截原因,例如$ua_type和原始$http_user_agent - 不推荐仅用
return 403:可搭配add_header X-Blocked-By "UA-Filter";,方便前端或 WAF 协同识别
扩展日志格式,让拦截可查可控
修改 log_format,把 UA 分类和原始值都写进日志行:
- 在
http块里定义:log_format main '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$ua_type" $request_time'; - 这样每条 403 日志都会带出是
scanner还是invalid,还能看到原始 UA 字符串,排查时不用再翻配置 - 配合
grep "403.*scanner" /www/wwwlogs/your-site.log就能秒筛攻击源
补充可观测性,防漏防扰
单纯拦截不够,要让防御“有迹可循”:
- 对高频触发拦截的 IP,用
limit_req限流(例如limit_req zone=ua_block burst=5 nodelay;),避免被反复试探 - 定期导出日志中的
$ua_type分布,用脚本统计 Top 10 拦截类型,动态优化规则——比如某天gptbot突增,就确认是否需放行或加更细粒度判断 - 注意边界:部分 API 客户端、内网健康检查可能 UA 极简,建议先记录不拦截(
set $ua_action "log_only";),观察一周再决定是否升级为阻断


















