在CentOS服务器上,fail2ban配合firewalld实现联动封禁,核心原理就是fail2ban检测到暴力破解、异常登录等行为后,自动调用firewall-cmd命令将攻击IP加入富规则(rich rule)进行临时或永久封禁。这套组合比单独使用iptables更灵活、更易管理,因为firewalld是CentOS 7/8/9默认的防火墙管理工具,支持动态规则而不需要重启服务。具体操作分三步走:安装配置fail2ban、编写对应的jail规则、设置action脚本让fail2ban调用firewalld接口。下面我把每一步的细节、踩坑点和优化方案全部讲透。

一、为什么要用fail2ban+firewalld而不是其他方案

很多人第一反应是用iptables直接写规则,或者用denyhosts之类的工具。但fail2ban的优势在于它能实时分析日志文件,自动识别异常模式,不需要你手动写复杂的正则。而firewalld作为CentOS原生防火墙,支持zone概念、rich rule动态添加、永久规则和临时规则分离,管理起来比直接操作iptables链清晰得多。两者联动的好处是:封禁是动态的,到时间自动解除;规则持久化在firewalld里,重启不丢失;而且你可以随时用firewall-cmd --list-all查看当前被封的IP,运维非常直观。

需要注意的是,CentOS 7默认用firewalld,CentOS 8/9也是。如果你的系统还在用CentOS 6,那是iptables+ipchains的时代,这套方案不适用。另外,如果你同时装了ufw或者直接关了firewalld用iptables,那联动就没意义了,先统一用firewalld。

二、安装和基础配置fail2ban

先装fail2ban,CentOS 7用yum,CentOS 8/9用dnf:

yum install -y fail2ban

或者:

dnf install -y fail2ban

装完之后启动并设为开机自启:

systemctl enable fail2ban
systemctl start fail2ban

fail2ban的主配置文件在/etc/fail2ban/jail.conf,但官方强烈建议不要直接改这个文件,而是复制一份到jail.local,所有自定义都写在jail.local里。这样以后升级fail2ban不会覆盖你的配置。

cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

打开jail.local,先看默认的[DEFAULT]段,几个关键参数要理解:

[DEFAULT]
bantime = 10m          # 封禁时长,默认10分钟
findtime = 10m         # 检测时间窗口,默认10分钟内
maxretry = 5           # 最多尝试次数,超过就封
ignoreip = 127.0.0.1/8 # 忽略的IP,别把自己封了

bantime建议根据业务调整,SSH暴力破解一般设1小时到24小时都行。findtime和maxretry配合使用,意思是"在10分钟内失败5次就封"。如果你的服务器访问量大、正常用户偶尔输错密码,可以把maxretry调高到8或10,避免误封。

三、编写firewalld联动的action脚本

fail2ban自带了一些action,但默认没有firewalld的。你需要自己写一个。在/etc/fail2ban/action.d/目录下创建一个文件,比如firewallcmd-ipset.conf:

[Definition]
actionstart = 
actionstop = 
actioncheck = 
actionban = firewall-cmd --add-rich-rule='rule family="ipv4" source address="" drop' --permanent
             firewall-cmd --reload
actionunban = firewall-cmd --remove-rich-rule='rule family="ipv4" source address="" drop' --permanent
             firewall-cmd --reload

[Init]
name = firewallcmd-ipset

这里有几个关键点要说清楚。第一,--permanent表示规则写入永久配置,重启firewalld不会丢。第二,--reload是让规则立即生效,不需要重启整个防火墙服务。第三,actionban里的<ip>会被fail2ban自动替换成实际的攻击IP。第四,如果你想用ipset而不是rich rule(ipset性能更好,适合封禁大量IP),可以改用:

actionban = ipset add fail2ban-ssh-exist
             firewall-cmd --add-rich-rule='rule family="ipv4" source ipset="fail2ban-ssh drop" --permanent
             firewall-cmd --reload

用ipset的好处是批量管理,一条rich rule引用一个ipset,里面可以塞几百上千个IP,而不是每个IP一条规则。对于高频率攻击场景,强烈推荐ipset方案。

创建完action文件后,还要创建对应的ipset(如果用ipset方案):

ipset create fail2ban-ssh hash:ip timeout 86400
ipset save fail2ban-ssh > /etc/sysconfig/ipset/fail2ban-ssh
systemctl enable ipset
systemctl start ipset

timeout 86400表示ipset里的条目24小时自动过期,相当于自动解封。这样即使fail2ban出问题没unban,ipset也会自己清理。

四、配置jail规则启用联动

回到jail.local,添加SSH的jail配置:

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/secure
maxretry = 5
bantime = 3600
action = firewallcmd-ipset

如果你用的是ipset方案,action就写上面创建的action名称。如果你想同时封禁多个服务,比如SSH和HTTP,可以分别建jail:

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/secure
maxretry = 5
bantime = 7200
action = firewallcmd-ipset

[http-auth]
enabled = true
port = http,https
filter = apache-auth
logpath = /var/log/httpd/*error.log
maxretry = 3
bantime = 1800
action = firewallcmd-ipset

注意logpath要根据你实际的日志路径来写。CentOS 8/9如果用的是nginx,日志路径是/var/log/nginx/error.log。filter用apache-auth或者nginx-auth都行,fail2ban自带了这些filter的正则定义。

五、测试验证和常见问题排查

配置完之后重启fail2ban:

systemctl restart fail2ban

然后用fail2ban-client查看状态:

fail2ban-client status sshd

你会看到当前被封的IP列表、封禁时间等信息。想手动测试,可以从另一台机器故意输错SSH密码5次,然后回来看fail2ban有没有记录:

fail2ban-client status sshd

同时用firewall-cmd验证规则是否真的加进去了:

firewall-cmd --list-all
firewall-cmd --list-rich-rules

常见问题有这么几个。第一,fail2ban启动报错说找不到action,检查action文件路径是不是在/etc/fail2ban/action.d/下面,文件名后缀必须是.conf。第二,firewall-cmd报权限错误,确保fail2ban是以root运行的,它默认就是root。第三,封禁了但攻击还能进来,检查你的jail里port写的对不对,SSH默认是22,如果你改了端口要对应改。第四,规则加了但没生效,确认你用了--permanent加--reload,光--permanent不加--reload的话规则要重启firewalld才生效。

六、进阶优化和安全建议

第一,建议把fail2ban的日志级别调高,方便排查问题。在jail.local的[DEFAULT]段加:

loglevel = INFO

第二,配合/etc/hosts.deny做双重保险。虽然fail2ban已经在防火墙层封了,但hosts.deny是TCP Wrappers层的,多一层没坏处。在/etc/hosts.deny里加:

sshd: ALL

然后在/etc/hosts.allow里放你信任的IP。不过现在很多服务默认不编译TCP Wrappers支持了,这一步看情况。

第三,如果你的服务器有公网IP且暴露了SSH端口,强烈建议同时改SSH默认端口,把22改成一个不常用的高位端口,比如22222或者38923。这不是必须的,但能大幅减少自动化扫描的命中概率。改完之后记得在jail里的port字段同步修改,在firewalld里也开放新端口。

第四,定期检查fail2ban的统计报告。fail2ban自带一个fail2ban-client status命令可以看每个jail的统计,你也可以装fail2ban-server的邮件通知功能,在jail.local里配置destemail和sender,被封的时候发邮件提醒你。对于个人服务器,这个功能很实用。

第五,关于解封策略。如果你发现某个IP被误封了(比如你自己的动态IP),可以手动解封:

fail2ban-client set sshd unbanip 1.2.3.4

或者直接用firewall-cmd删规则:

firewall-cmd --remove-rich-rule='rule family="ipv4" source address="1.2.3.4" drop' --permanent
firewall-cmd --reload

第六,对于高并发攻击场景(比如DDoS的SYN flood),fail2ban其实不太适合,因为它是基于应用层日志分析的,对网络层洪水反应太慢。这种情况需要在firewalld里直接配SYN proxy或者用更底层的防护手段。fail2ban最擅长的还是应对SSH暴力破解、Web登录暴力破解、邮件爆破这类应用层攻击。

七、完整配置示例汇总

最后给一个可以直接用的完整配置参考。jail.local关键部分:

[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
ignoreip = 127.0.0.1/8 192.168.1.0/24
backend = systemd

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/secure
maxretry = 5
bantime = 4h
action = firewallcmd-ipset

[sshd-ddos]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/secure
maxretry = 10
bantime = 24h
action = firewallcmd-ipset

这里我故意建了两个sshd jail,一个正常封禁5次,一个针对更激进的攻击设10次才封但封24小时。你可以根据实际需求调整。action.d里的firewallcmd-ipset.conf前面已经给了,直接用就行。

总结一下,fail2ban+firewalld联动封禁这套方案,部署简单、维护方便、效果立竿见影。核心就是写好action脚本让fail2ban能调用firewall-cmd,然后在jail里指定这个action。CentOS 7/8/9都能用,比iptables方案优雅得多。花半小时配好,你的服务器安全等级直接上一个台阶。