背景
公网上的 SSH 22 端口,每天被全球扫描器爆破几千次。改端口可以挡一部分,但治标不治本。fail2ban 通过扫描日志匹配失败登录,自动调用 iptables/nftables 拉黑攻击 IP——简单粗暴有效,是 Linux 服务器"装了不亏"的标配。
适用场景:
- SSH/RDP/FTP 暴破防御
- FRP 反向代理的"真实攻击者 IP"拉黑(frpc 只看到 127.0.0.1,必须在 frps 端 ban)
- Nginx 反代日志里的恶意访问拦截
- 自定义服务日志的异常行为检测
前置知识:
- Linux 基础(systemd、iptables)
- 正则表达式基础(fail2ban filter 用的就是正则)
一、fail2ban 工作原理
1
2
3
4
5
6
7
| 服务日志(sshd/auth.log/secure)
↓
[filter.d/*.conf 正则匹配] → 失败次数超阈值
↓
[jail.local 触发动作] → iptables/nftables DROP
↓
[封禁时间到] → 自动解封
|
关键组件:
- filter:正则规则(
/etc/fail2ban/filter.d/) - jail:封禁策略(
/etc/fail2ban/jail.local) - action:触发动作(iptables、route、cloudflare API 等)
- bantime / findtime / maxretry:封禁时长 / 检测窗口 / 失败次数阈值
二、安装
2.1 CentOS
1
2
| yum -y install epel-release
yum -y install fail2ban
|
2.2 Ubuntu / Debian
2.3 启动 + 开机自启
1
2
| systemctl enable --now fail2ban
systemctl status fail2ban
|
验证 iptables 规则已加载:
1
2
| iptables -L -n
# 应看到 f2b-sshd 等 chain
|
三、配置 SSH 防护(默认即开)
/etc/fail2ban/jail.local(注意是 .local 不是 .conf,.local 优先级高):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| [DEFAULT]
# 忽略的 IP(公司出口、内网)
ignoreip = 127.0.0.1/8 10.0.0.0/8 192.168.0.0/16
# 默认参数(每个 jail 可单独覆盖)
bantime = 24h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
# filter 内置 sshd.conf(Debian/Ubuntu) / sshd.conf(CentOS 在 filter.d/ 选)
filter = sshd
backend = systemd
# 日志路径(按发行版二选一)
logpath = %(sshd_log)s
|
%(sshd_log)s 是 fail2ban 内置变量,CentOS 是 /var/log/secure,Ubuntu 是 /var/log/auth.log,不用手填。
reload 生效:
1
2
3
| fail2ban-client reload
# 或
systemctl restart fail2ban
|
验证:
1
2
| fail2ban-client ping # 返回 Server replied: pong
fail2ban-client status sshd # 看当前被 ban 的 IP
|
四、FRP 反代场景的关键配置
核心痛点:frpc 看到的访问者 IP 永远是 127.0.0.1(因为 frps 是 localhost 转发的),必须在 frps 所在的 VPS 上配 fail2ban,按 frps 日志里的真实 IP 拉黑。
4.1 启用 FRP 日志
/usr/local/games/rp/frps.ini:
1
2
3
4
5
| [common]
bind_port = 7000
log_file = /var/log/frps.log
log_level = info
log_max_days = 7
|
4.2 编写 filter
/etc/fail2ban/filter.d/frps.conf:
1
2
3
4
| [Definition]
# frps 日志格式: get a user connection [1.2.3.4:5678]
failregex = ^.*get a user connection \[<HOST>:[0-9]*\]
ignoreregex = ^.*\[.*gateway.*
|
<HOST> 是 fail2ban 的内置占位符,匹配 IP 段。
4.3 编写 jail
/etc/fail2ban/jail.local 追加:
1
2
3
4
5
6
7
8
| [frps]
enabled = true
filter = frps
logpath = /var/log/frps.log
findtime = 3m
maxretry = 3
bantime = 120m
action = iptables-allports[name=frps, protocol=all]
|
多个协议分离 jail(更精细):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| [frps-ssh-ban]
enabled = true
filter = frps-ssh-ban
logpath = /var/log/frps.log
findtime = 3m
maxretry = 3
bantime = 120m
action = iptables-allports[name=frps-ssh-ban, protocol=tcp]
[frps-rdp-ban]
enabled = true
filter = frps-rdp-ban
logpath = /var/log/frps.log
findtime = 3m
maxretry = 6
bantime = 30m
action = iptables-allports[name=frps, protocol=tcp]
[frps-ftp-ban]
enabled = true
filter = frps-ftp-ban
logpath = /var/log/frps.log
findtime = 5m
maxretry = 30
bantime = 60m
action = iptables-allports[name=frps, protocol=tcp]
|
filter 内容(按代理名分组):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # /etc/fail2ban/filter.d/frps-ssh-ban.conf
[Definition]
failregex = ^.*\[.*ssh.*\] get a user connection \[<HOST>:[0-9]*\]
ignoreregex = ^.*\[.*gateway.*
# /etc/fail2ban/filter.d/frps-rdp-ban.conf
[Definition]
failregex = ^.*\[.*RDP.*\] get a user connection \[<HOST>:[0-9]*\]
ignoreregex =
# /etc/fail2ban/filter.d/frps-ftp-ban.conf
[Definition]
failregex = ^.*\[.*FTP_21.*\] get a user connection \[<HOST>:[0-9]*\]
ignoreregex =
|
测试正则匹配(上线前必做):
1
2
| fail2ban-regex /var/log/frps.log /etc/fail2ban/filter.d/frps.conf
# 末尾会输出匹配行数
|
五、Nginx 日志防护
如果用 Nginx 反代 RDP/SSH(stream 四层 + 自定义 log_format):
5.1 自定义日志格式
1
2
3
4
5
| log_format proxy '[$time_local] $protocol status:$status, '
'bytes client:$bytes_sent/$bytes_received $session_time, '
'client: $remote_addr, upstream:"$upstream_addr", '
'bytes upstream:$upstream_bytes_sent $upstream_bytes_received';
access_log /var/log/nginx/proxy.log proxy;
|
5.2 写 filter
1
2
3
4
| # /etc/fail2ban/filter.d/nginx-3399.conf
[Definition]
failregex = ^.*client: <HOST>, upstream:"10\..*:3389"
ignoreregex =
|
5.3 写 jail
1
2
3
4
5
6
7
8
| [nginx-3399]
enabled = true
filter = nginx-3399
logpath = /var/log/nginx/proxy.log
findtime = 5m
maxretry = 10
bantime = 60m
action = iptables-multiport[name=nginx-3399, port="3399", protocol=tcp]
|
六、多日志文件支持
轮转日志场景(按天/按大小切割):
1
2
3
4
5
6
7
| # 通配符
logpath = /var/log/vpn_*.log
# 多个文件
logpath = /var/log/vpn_20191007.log
/var/log/vpn_20191008.log
/var/log/vpn_20191009.log
|
⚠️ 等号要对齐(fail2ban INI parser 严格)。
七、日常管理命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| # 服务状态
fail2ban-client ping
# 列出所有 jail
fail2ban-client status
# 查看指定 jail
fail2ban-client status sshd
# 手动封 IP
fail2ban-client set sshd banip 1.2.3.4
# 手动解封
fail2ban-client set sshd unbanip 1.2.3.4
# 解封所有
fail2ban-client unban --all
# 重载配置(不重启服务)
fail2ban-client reload
# 看日志
tail -f /var/log/fail2ban.log
|
直接操作 iptables(不进 fail2ban 的快封):
1
2
3
4
5
6
7
8
| # 手动封
iptables -A INPUT -s 1.2.3.4 -j DROP
# 查询
iptables -L -n --line-number
# 删除 INPUT 第 3 条规则
iptables -D INPUT 3
|
八、常见坑
8.1 封了之后 SSH 自己进不去
症状:把整个机房 IP 段封了。
对策:
ignoreip 把内网/常用出口 IP 段加白名单- 至少留一个"急救"通道(如 IPMI、Console、备用跳板)
8.2 重启服务后封禁丢失
iptables 规则在重启后清空。对策:
1
2
3
4
5
6
7
8
| # CentOS
yum install iptables-services
systemctl enable iptables
service iptables save
# Ubuntu
apt install iptables-persistent
netfilter-persistent save
|
8.3 多网卡机器
chain = INPUT 即可默认全局生效。如果要区分内/外网卡,用 chain = FORWARD 配合路由。
8.4 日志被 logrotate 改名后 fail2ban 跟丢
backend = polling 改 backend = systemd(systemd 日志自动跟踪),或者在 /etc/logrotate.d/ 加 postrotate 通知 fail2ban。
九、卸载
1
2
3
4
5
6
| # 清掉所有 fail2ban iptables 规则
iptables -L | grep f2b | awk '{print $NF}' | xargs -I{} iptables -F {}
/etc/init.d/fail2ban stop # 或 systemctl stop fail2ban
apt remove fail2ban -y
apt purge fail2ban -y # 删配置
|
小结
fail2ban 0.10+ 的核心是 filter 正则 + jail 策略:
- SSH 默认就开(
apt install 装完自带) - FRP 场景最关键——必须在 frps 端按 frps.log 真实 IP 拉黑
- jail.local 永远比 jail.conf 优先(升级不会覆盖)
- 测试正则:
fail2ban-regex 上线前必跑 - iptables 持久化 否则重启失效
下一步:
- 配合
cloudflare action 封 CDN 后面的恶意 IP - 用
recidive jail 二次封禁"被解封又来"的攻击者 - 集中化管理:多台机器用 fail2ban-web 或自建 dashboard
2024 视角:fail2ban 仍是经典,但 nftables 时代来了
2015 那篇基于 iptables。2024 视角下 nftables 已成事实标准——fail2ban 1.0+(2022-10 发布)已支持 nftables。
一、nftables 替代 iptables 是大势所趋
- RHEL 8/9 / Fedora / Ubuntu 22.04+ / Debian 12+ 默认都是 nftables(
iptables 命令其实是 nftables 的兼容层)。 - fail2ban 1.0.x(2022-10 起)支持
banaction = nftables-multiport:
1
2
3
4
5
| # /etc/fail2ban/jail.local
[DEFAULT]
banaction = nftables-multiport
banaction_allports = nftables-allports
chain = input
|
- nftables 优势:
- 原子化更新(nft 是事务性的,iptables 改一条规则要逐条 add/delete)
- 规则集更紧凑(nft 的语法比 iptables 短 30-50%)
- 内建 set / map(无需 ipset 单独装)
1
2
| # nftables 查 fail2ban 规则
nft list chain inet filter f2b-sshd
|
二、recidive 监狱——“二次封禁"防反复
2015 那篇提到 recidive。2024 实际配置:
1
2
3
4
5
6
7
8
9
| # /etc/fail2ban/jail.local
[recidive]
enabled = true
filter = recidive
logpath = /var/log/fail2ban.log
bantime = 1w
findtime = 1d
maxretry = 5
banaction = %(banaction)s
|
效果:被解封后又来攻击的 IP,封一周。
三、Cloudflare Action——封 CDN 后面的 IP
1
2
3
4
5
6
7
8
9
10
11
| # /etc/fail2ban/action.d/cloudflare.conf
[Definition]
actionban = curl -s -X POST "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/firewall/access_rules/rules" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Content-Type: application/json" \
--data '{"mode":"block","configuration":{"target":"ip","value":"<ip>"},"notes":"Fail2ban"}'
actionunban = curl -s -X DELETE "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/firewall/access_rules/rules/<RULE_ID>" \
-H "Authorization: Bearer <API_TOKEN>"
[Init]
RULE_ID =
|
1
2
3
4
| [sshd-cloudflare]
enabled = true
filter = sshd
action = cloudflare
|
四、fail2ban + Telegram 告警
1
2
3
4
5
| # /etc/fail2ban/action.d/telegram.conf
[Definition]
actionban = curl -s -X POST "https://api.telegram.org/bot<TOKEN>/sendMessage" \
-d chat_id=<CHAT_ID> \
-d text="Fail2ban: <ip> banned (<failures> failures)"
|
五、fail2ban 1.1 的新特性(2023-10)
- 持久化 SQLite 数据库(
dbfile = /var/lib/fail2ban/fail2ban.sqlite3)——重启后封禁不丢。 database 升级:以前是 BerkeleyDB(要装 python3-pyasn1),现在用 SQLite。- 正则引擎优化:Python 3.11+ 提速 20%。
六、CrowdSec:fail2ban 的"现代继任者”
- CrowdSec(Go 写的现代版 fail2ban)2024 已成为更激进的选择:
1
2
3
4
| # 装
curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash
sudo apt install crowdsec
sudo apt install crowdsec-firewall-bouncer-iptables
|
七、fail2ban 在 Docker 时代的"困境"
- fail2ban 装在 host 上时,它看不到容器里的日志(除非 mount log 目录到 host)。
- fail2ban 装在容器里时,它改不了 iptables/nftables(容器网络是隔离的)。
- 2024 推荐方案:
- 方案 A:fail2ban 装 host,把容器日志 mount 出来(适合简单场景)
- 方案 B:用 CrowdSec + bouncer in container(更适合 K8s)
- 方案 C:直接上 WAF(Cloudflare / ModSecurity)—— 应用层防护
源文档:os/linux/第三方tools/safe/fail2ban/fail2ban.md(filter 模板、jail.local 多协议、FRP 集成)