fail2ban 装上了却一个 IP 都没封,问题出在反向代理后面
家里的 NAS 上跑着几个自建服务,全都走 nginx 反代统一暴露 443。前阵子翻 access.log 的时候发现一堆 /wp-login.php、/.env、/adminer.php 的请求,同一个 IP 一分钟能刷几十条,明显是扫描器在撞库。想着装个 fail2ban 自动封一下,配完之后第二天满怀期待地敲了 fail2ban-client status sshd,结果 banned IP list 是空的。
不是没日志,也不是 fail2ban 没起来,纯粹是我踩了两个坑。
第一个坑:日志里记的全是反代自己的 IP
自建服务在 Docker 里跑,nginx 在外面转发,$remote_addr 拿到的永远是反代的地址,也就是 127.0.0.1 或者 docker 网桥那个网段。fail2ban 老老实实按日志封了一批"攻击者",实际上封的全是我自己内网的转发地址。表现就是:jail 是 enabled,filter 也能匹配上,但封完没任何效果,扫描照常进来。
先确认一下日志里到底是什么:
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
如果排在第一的是 127.0.0.1 或者 172.x,那就得先让 nginx 记下真实客户端 IP。在 http 块里加:
set_real_ip_from 127.0.0.1;
set_real_ip_from 172.17.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
real_ip_recursive on 这句挺关键。XFF 是一串逗号分隔的地址链,开了 recursive,nginx 会从右往左跳过所有 set_real_ip_from 里声明的可信地址,取第一个不可信的当真实 IP。不开的话它只会取最右边那个,也就是你自己的反代 IP,等于白配。
改完记得先 nginx -t 再 reload,然后用 curl 手动验一把:
curl -H "X-Forwarded-For: 1.2.3.4" https://你的域名/
tail -1 /var/log/nginx/access.log
这一行的第一列变成 1.2.3.4 才算配对了。这一步别省,我第一次就是没验,白等了一晚上。
第二个坑:filter 匹配到的是不是真 IP
fail2ban 自带了一堆 filter,我没上来就写正则,而是先用 fail2ban-regex 在已有日志上离线跑一遍:
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-botsearch.conf
它的输出里最有用的是两段:一段是匹配到的行数,另一段是匹配出来的地址列表(Addresses)。直接看这个列表就知道 fail2ban 会封谁——如果里面还是一片 127.0.0.1,说明上一步没生效,回去改 nginx,别在这儿死磕正则。
确认 filter 靠谱之后再写 jail。配置一律写 /etc/fail2ban/jail.local,不要去改 jail.conf,升级的时候会被覆盖:
[nginx-botsearch]
enabled = true
port = http,https
filter = nginx-botsearch
logpath = /var/log/nginx/access.log
maxretry = 5
findtime = 600
bantime = 86400
ignoreip = 127.0.0.1/8 10.0.0.0/8 192.168.0.0/16
三个时间参数的含义按顺序是:600 秒内命中 5 条就封,封 86400 秒(一天)。bantime 写 -1 是永久封,我一般不这么干,误封自己一次就得跑机房。
ignoreip 一定要把自己的出口 IP 和内网段加进去,不然手机在外网访问时输错几次密码就把自己关外面了。真被封了也不用慌,解封一条命令的事:
fail2ban-client set nginx-botsearch unbanip 1.2.3.4
第三个坑:banaction 和 nftables
还有一个比较隐蔽的。fail2ban 默认 banaction = iptables-multiport,但新一点的 Debian / Ubuntu 底层已经是 nftables,iptables 命令只是兼容层。有的环境能凑合用,有的直接封不生效,journalctl -u fail2ban 里会刷一堆加规则的报错。
先看看你手上有哪些 action 可用:
ls /etc/fail2ban/action.d/ | grep -i nftables
如果有 nftables-multiport.conf,就在 jail.local 的 DEFAULT 段或者对应 jail 里写:
banaction = nftables-multiport
改完别用 restart,用 fail2ban-client reload。重载前可以 fail2ban-client -t 做一次语法检查,能提前发现参数写错。
验证是否真的落到内核里,两条路:
fail2ban-client status nginx-botsearch
nft list ruleset | grep -i f2b
前一条看 "Currently banned" 的数字,后一条确认规则真的进 nftables 了。两边都有东西,才算闭环。
顺带说下 Docker 的情况
我一开始想偷懒把 fail2ban 也塞进容器里,很快就放弃了:容器里看不到宿主机 nginx 的日志文件,得把 /var/log/nginx 挂进去;而且封禁需要动 netfilter,容器得加 --cap-add=NET_ADMIN,网络模式也得折腾。最后是把 nginx 日志目录挂到宿主机一个固定路径,fail2ban 直接在宿主机上装,读挂载出来的日志文件,省事太多。
日常看的几条命令
fail2ban-client status # 列出所有 jail
fail2ban-client status nginx-botsearch # 看单个 jail 封了几个
journalctl -u fail2ban -f # 排查规则加载异常
这套配完之后,access.log 里那种成片的扫描请求确实少了很多,主要是同一批 IP 撞几次就被踢掉了,不会再刷满整个日志。回头看其实每一步都不难,难的是不知道该往哪查——建议顺序就是:先确认日志里的 IP 是真的,再用 fail2ban-regex 离线验 filter,最后再确认封禁动作真的落到防火墙里。三步都过了,基本就不会出现"装了却一个都没封"这种事。
- 点赞
- 收藏
- 关注作者
评论(0)