Nginx 防爬虫结合 fail2ban 自动封禁 IP 的教程。
Nginx 通过 UA 匹配、限速和敏感路径拦截识别爬虫并返回 444 写入日志,fail2ban 按正则统计日志,10 分钟内触发 3 次即封禁 IP 24 小时,实现低成本自动防爬。
结论:Nginx 负责“识别并记录”爬虫行为,fail2ban 负责“读取日志并封禁 IP”,两者组合是成本最低、当天就能上线的自动防爬方案——Nginx 侧用限速 + UA 匹配 + 敏感路径拦截,爬虫命中后返回 444 并写入日志;fail2ban 按正则统计日志,10 分钟内触发 3 次即封禁 24 小时。
整体链路是怎么跑的
核心逻辑一句话:Nginx 只做“标记”,不做“封禁”,封禁交给系统防火墙。
具体链路分四步:① 在 nginx.conf 里定义带 $http_user_agent 和 $status 的 log_format,保证日志可被正则解析;② 用 limit_req 限速、map 匹配异常 UA、location 拦截高频探测路径,命中即 return 444;③ fail2ban 的 filter 用正则匹配日志中状态码为 444 的行;④ jail 里设置 maxretry=3、findtime=600、bantime=86400,超阈值直接调 iptables 丢包。
这里解释两个术语:fail2ban 是一个读取日志文件、按正则规则统计失败次数并自动调用防火墙封禁 IP 的开源工具;444 是 Nginx 的非标准状态码,含义是“直接关闭连接、不返回任何响应体”,比返回 403 更省带宽和 CPU,且对爬虫来说等同于连接被重置。
Nginx 侧要改哪些配置
第一步必须改日志格式,否则 fail2ban 无法区分“被拦截”和“正常请求”。在 http 块加:
log_format anti_spider '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent"';
access_log /var/log/nginx/access.log anti_spider;
第二步做 UA 与路径识别:
map $http_user_agent $bad_ua {
default 0;
~*(scrapy|python-requests|curl|wget|go-http-client|okhttp|headlesschrome) 1;
}
limit_req_zone $binary_remote_addr zone=perip:10m rate=2r/s;
server {
location ~* (wp-login\.php|xmlrpc\.php|phpmyadmin|\.env|\.git/) { return 444; }
location / {
if ($bad_ua) { return 444; }
limit_req zone=perip burst=5 nodelay;
limit_req_status 444;
}
}
rate=2r/s 表示单 IP 每秒 2 个请求,burst=5 nodelay 允许 5 个突发请求立即放行、超出直接拒。注意 limit_req_status 必须显式设为 444,默认是 503,503 会和后端故障日志混在一起,导致 fail2ban 误封正常用户。
fail2ban 的 filter 和 jail 怎么写
filter 文件放在 /etc/fail2ban/filter.d/nginx-badbot.conf:
[Definition]
failregex = ^<HOST> - .* "(GET|POST|HEAD) [^"]*" 444
^<HOST> - .* "(GET|POST|HEAD) [^"]*" (400|403)
ignoreregex =
jail 写在 /etc/fail2ban/jail.local(不要直接改 jail.conf,升级会被覆盖):
[nginx-badbot]
enabled = true
port = http,https
filter = nginx-badbot
logpath = /var/log/nginx/access.log
maxretry = 3
findtime = 600
bantime = 86400
ignoreip = 127.0.0.1/8 你的办公固定IP
action = iptables-multiport[name=nginx-badbot, port="http,https", protocol=tcp]
参数含义:maxretry 是允许的失败次数,findtime 是统计窗口秒数(600 秒),bantime 是封禁时长(86400 秒即 24 小时)。写完后先跑 fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-badbot.conf 验证正则能命中历史日志,再 systemctl restart fail2ban,最后用 fail2ban-client status nginx-badbot 查看当前封禁列表。
最容易踩的三个坑
第一个坑:站点挂了 CDN,封的是 CDN 节点 IP。 如果流量经 Cloudflare 或国内 CDN 回源,$remote_addr 拿到的是回源节点 IP,一封就是全站不可访问。必须在 Nginx 里启用 real_ip 模块还原真实 IP:set_real_ip_from CDN回源段; real_ip_header X-Forwarded-For; real_ip_recursive on;,且回源段要写全,漏写一段就会把 CDN 节点封掉。
第二个坑:误封搜索引擎。 Googlebot、Baiduspider 抓取频率天然高于普通用户,容易被 limit_req 打成 444。做法是把官方公布的反向解析 IP 段加入 ignoreip,或者单独为已验证的搜索引擎 UA 放开限速。
第三个坑:日志轮转后 fail2ban 失效。 Nginx 默认按天切割日志,fail2ban 依赖 inode 跟踪,logrotate 里若用了 copytruncate 以外的模式,需要确认 create 后的新文件权限对 fail2ban 可读,建议在 logrotate 配置里加 postrotate 调用 fail2ban-client reload nginx-badbot。
封禁生效后可用 fail2ban-client set nginx-badbot banip 1.2.3.4 手动加黑、unbanip 解封,配合 iptables -L -n | grep 1.2.3.4 确认规则已落地。这类 Nginx 调优与故障排查的细节,在 GJ站长论坛「技术交流 - 运维」版块(子分类:运维、面板)里可以继续交流,等级规则见 https://www.gj0.com/levels.html 。
整套方案的价值在于:Nginx 侧只加约 20 行配置、fail2ban 侧两个文件,就能把 90% 以上的低质量采集器挡在应用层之外,且封禁策略可随日志正则随时调整,不需要改一行业务代码。
转载请注明出处,版权归原作者所有。