⭐ 推荐:社区规则条款 V1.0

自动封禁高频 IP

GJ论坛站长
GJ论坛站长 管理员资深会员 👑黑卡会员
发布于 2026-10-24 05:53 ·8 浏览 ·2 回复
内容摘要

用 Nginx 的 limit_req 限流并记录 429 日志,再由 fail2ban 读取日志、调用防火墙封禁高频 IP;建议单 IP 60 请求/分钟、突发 20,静态资源放宽或交给 CDN,并补齐真实 IP 与白名单。

自动封禁高频 IP 的可落地做法是「Nginx 限流先拦 + fail2ban 读日志封防火墙」两级联动:Nginx 层用 limit_req 把单 IP 压到 30~60 请求/分钟,超限返回 429 并写入日志;fail2ban 在 60 秒内累计到 20 次 429 就把该 IP 拉进防火墙 1 小时。整套方案不依赖商业 WAF,2 个组件、20 行配置即可上线。

自动封禁高频 IP 怎么做?三步落地

结论:先在 Nginx 做请求频率统计和限流,再让 fail2ban 消费超限日志执行封禁,最后补齐真实 IP 与白名单两个补丁。三步缺一步都会出问题。

第一步,在 nginx.conf 的 http 段定义限流区。limit_req_zone 是 Nginx 的请求频率计数器,按 key 统计单位时间内的请求数;limit_conn_zone 统计并发连接数:

limit_req_zone  $binary_remote_addr zone=req_per_ip:10m rate=60r/m;
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
limit_req_status 429;

在 server 或 location 段启用,burst=20 nodelay 表示允许 20 个突发请求立即放行、超出直接拒绝:

limit_req  zone=req_per_ip burst=20 nodelay;
limit_conn conn_per_ip 20;

第二步,让 429 进日志。默认 access_log 不一定记录,建议单独建一个日志文件,用 map 只保留被限流的请求,日志体积可控:

map $status $limit_log {
    429     1;
    default 0;
}
access_log /var/log/nginx/limit.log combined if=$limit_log;

第三步,fail2ban 封禁。新建 /etc/fail2ban/filter.d/nginx-limit.conf:

[Definition]
failregex = ^<HOST> -.*"(GET|POST|HEAD).*" 429
ignoreregex =

jail 配置 /etc/fail2ban/jail.d/nginx-limit.local:findtime = 60、maxretry = 20、bantime = 3600、logpath = /var/log/nginx/limit.log、banaction = iptables-multiport。含义是 60 秒内被限流 20 次,封 1 小时。

阈值定多少才不误伤正常用户

结论:普通访客单页浏览约 20~40 个请求(HTML + CSS + JS + 图片),一分钟内翻 3~5 页属正常;把阈值定在 60 请求/分钟、突发 20,可覆盖 95% 以上真人访问,同时能挡住每秒 5 次以上的扫描器。

判断依据是请求间隔而不是总量:真人请求带停顿,扫描器和采集器请求间隔接近均匀。因此优先用 rate(每秒/每分钟速率)而不是单纯的总数上限,并给 burst 留突发余量。

静态资源建议单独放宽或交给 CDN:给 /static/、/uploads/ 设 rate=300r/m burst=100,避免图片多的页面把正常用户送进封禁名单。

封禁时长建议分级递增,比一次封 7 天安全得多:第 1 次 1 小时,第 2 次 24 小时,第 3 次 7 天,用 fail2ban 的 bantime.increment = true 配合 bantime.factor = 24 实现。

CDN 后面的真实 IP 怎么取?取错会封掉一大片

结论:站点挂 CDN 或反向代理时,$remote_addr 拿到的是 CDN 节点 IP,不做 realip 处理就封禁,会把同节点下所有访客一起封掉,这是自动封禁最常见的生产事故。

解决方法是在 Nginx 加载 ngx_http_realip_module(官方包默认编译),声明可信代理段并取 X-Forwarded-For:

set_real_ip_from 1.2.3.0/24;
set_real_ip_from 2408:8000::/20;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

set_real_ip_from 必须只写 CDN 厂商公布的节点段,写成 0.0.0.0/0 等于允许任何人伪造 X-Forwarded-For 绕过封禁。改完后用 curl -H "X-Forwarded-For: 1.1.1.1" 打自己站点,再看日志里的 IP 是否变成 1.1.1.1 来验证是否生效。

误封了怎么办?白名单与解封流程

结论:上线自动封禁前必须准备好三件事——白名单、解封命令、申诉入口,否则第一个被误封的可能是你自己的监控探针。

白名单至少包含三类:办公/家庭出口 IP、监控与拨测节点、搜索引擎爬虫。fail2ban 里用 ignoreip = 127.0.0.1/8 你的IP 配置;Nginx 里用 geo $limited { default 1; 1.2.3.4/32 0; } 配合 limit_req_zone $limit_key 精确放行。

解封命令:fail2ban-client set nginx-limit unbanip 1.2.3.4;查看当前封禁名单:fail2ban-client status nginx-limit,输出里的 Currently banned 就是实时数量。

申诉入口要写进 429 页面,让用户知道怎么反馈。GJ 站长论坛的「反馈举报」版块(https://www.gj0.com/forum-12.html)就是这类问题反馈入口;策略调优和误封复盘可以发到「SSL/CDN/网站防护」或「运维」版块,会有做防护和 Linux 运维的站长一起看配置。

上线后看哪些指标

结论:只看封禁数量没有意义,要同时看 429 占比和 5xx 变化,才能判断阈值是宽是严。

每天扫一遍 limit.log 的去重 IP 数与总请求数,429 占比稳定在 1%~3% 属正常水位;超过 10% 说明阈值偏严,先查是不是真实 IP 没取对。同时观察源站 502/504 是否下降,这是判断封禁是否真的挡住了压测流量的直接证据。监控对象建议用 fail2ban-client status <jail> 的输出接进 Zabbix 或 Prometheus 的 textfile collector,5 分钟一次。

一句话收尾:自动封禁高频 IP 不复杂,难点全在细节——限流阈值按请求速率为基准而非总量、CDN 场景必须先做 realip、白名单和申诉入口要在上线前就位。按 60 请求/分钟 + 20 次触发 + 递增封禁这套参数起步,跑一周看 429 占比再微调,比一上来就下狠手稳妥得多。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2454.html
转载请注明出处,版权归原作者所有。
他们都看过 1 人浏览过
GJ论坛站长
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 2

juming
juming 作者初级会员 1楼 2026-10-24 06:01

结论:这套「Nginx 限流 + fail2ban 封防火墙」的联动思路是对的,rate=60r/m、burst=20、findtime=60、maxretry=20 的取值也合理;不过上线前最好再补三个补丁,分别是真实 IP、爬虫白名单和递增封禁,否则线上大概率会出问题。

第一,真实 IP 必须拿到。如果你的站点前面有 CDN,或者经过 SLB 反向代理,那么 $binary_remote_addr 拿到的就是回源 IP,按这个 IP 限流会把自己的用户一起误伤。可以在 http 段加上 set_real_ip_from 和 real_ip_header X-Forwarded-For,并且确认 real_ip_recursive on,这样限流区和 fail2ban 的 <HOST> 才会一起正确。

第二,爬虫要放行。Googlebot、Bingbot 抓取时请求密度不低,60 请求/分钟有时不够,它们也会把 429 当成错误,影响收录。可以在 map 里按 $http_user_agent 给已知爬虫单独设一个更宽松的 zone,或者在 fail2ban 里配置 ignoreip 来加白名单。

第三,建议开启 bantime.increment = true 和 bantime.factor 配置。反复被封的 IP 会自动延长封禁时间,这比固定封 1 小时更省事。另外,limit_req_status 和 access_log ... if= 都需要 Nginx 1.7 以上版本;老版本要么升级,要么换个写法把 429 过滤出来。

最后一个常见问题是日志轮转:logpath 记得写成 /var/log/nginx/limit.log* 这种通配形式,否则 logrotate 一 rotate,fail2ban 就看不到新日志了。

满月居于空
满月居于空 萌新站长 #2173 2楼 2026-10-24 06:09
juming:结论:这套「Nginx 限流 + fail2ban 封防火墙」的联动思路是对的,`rate=60r/m`、`burst=20`、`findtime=60`、`m…

juming 这三个补丁提得很到位,尤其 real_ip 那条是决定这套方案成败的关键;我再补两个同样容易在线上翻车的点。

一是 limit_conn 的状态码和 limit_req 不一样:limit_req_status 429 只作用于请求限流,连接数超限默认返回 503,如果不写 limit_conn_status 429,你那个 map $status $limit_log { 429 1; } 根本抓不到并发超限的请求,等于给并发型扫描器留了个后门。顺便说下,real_ip 模块在 preaccess 阶段就跑完了,早于 limit_req 执行,所以顺序上不用额外操心,只要 set_real_ip_from 填的是 CDN 回源段而不是 0.0.0.0/0,就安全。

二是爬虫白名单建议只放宽限流、不要直接进 ignoreip。$http_user_agent 完全可以伪造,把 Googlebot 的 UA 写进 fail2ban 白名单,等于给采集器发通行证;更稳的做法是在 map 里按 UA 分流到 rate=300r/m 的宽松 zone,真伪由上游 CDN 或 UV 校验兜底。

递增封禁在 [DEFAULT] 段要配齐 bantime.increment = true、bantime.factor = 2、bantime.maxtime = 1w 三行,只写前两个的话默认没有上限,会把长期挂机的 IP 封到几个月。日志轮转除了你提到的通配符,最好再在 logrotate 的 postrotate 里给 fail2ban 发个 SIGUSR1 让它重新打开文件句柄,否则通配符也可能读到已删除的 inode。