真正起效的是将 $http_user_agent 与请求路径、状态码联动形成可观测+可拦截闭环;通过自定义 log_format 暴露 ua、uri、status 三要素,用 map 模块标记可疑组合,并在 location 中结合 limit_req 和 return 实现分级阻断。

直接在 log_format 中加入 $http_user_agent 是基础动作,真正起效的是把它和请求路径、状态码联动起来,形成可观测 + 可拦截的闭环。重点不是“记录 UA”,而是让 UA 成为判断依据之一,配合 URI 和 status 实现精准识别与响应。
配置增强型日志格式,暴露关键维度
默认日志不包含足够信息,需自定义 format 显式暴露三要素:UA、URI、status。例如:
log_format bot_audit '$time_iso8601 | $remote_addr | "$request_uri" | $status | "$http_user_agent" | $request_time | $body_bytes_sent';
这里把 $request_uri 放在 $status 前,便于后续用 awk 或日志分析工具按“路径+状态码”组合筛选。比如快速定位 /api/user?uid=1 返回 200 但 UA 是 python-requests 的请求链。
用 map 模块标记“UA+路径+状态”可疑组合
单纯匹配 UA 容易误伤,加入路径和状态上下文能大幅降低误判。例如拦截对登录接口的暴力试探:
map "$http_user_agent:$request_uri:$status" $is_suspicious_login {
default 0;
"~*python-requests:/login:200" 1;
"~*curl:/auth/token:401" 1;
"~*go-http-client:/api/v1/users:403" 1;
}
这个 map 不依赖 if,性能高,且可扩展——同一行就能表达“某 UA 访问某路径返回某状态”的异常模式,比单独过滤 UA 更贴近真实攻击行为。
在 location 中结合 limit_req 和 return 实现分级阻断
标记出可疑请求后,不能只靠日志看,要立刻干预。推荐两种策略:
- 对已确认恶意的 UA+路径组合(如 sqlmap 扫描 /admin),直接 return 403;
- 对模糊但高频的试探(如大量 404 + 空 UA + 遍历 /wp-content/),启用限流:limit_req zone=scanburst burst=3 nodelay;
- 避免在 if 中嵌套复杂逻辑,把判断统一放在 server 块顶层,用 $is_suspicious_login 触发对应动作。
验证阻断是否生效,盯住日志里的“试探痕迹”
部署后,用以下命令实时观察是否捕获到典型试探行为:
tail -f /var/log/nginx/bot_audit.log | grep -E "(401|403|404).*python-requests|curl|sqlmap"
重点关注三类组合:
• 大量 401 后紧跟 200(爆破成功)
• 同一 IP 在 1 分钟内对 /api/* 返回 10+ 次 404(目录遍历)
• /health 或 /status 路径被非浏览器 UA 高频访问(探测接口)











