本文约 5,699 字,预计阅读 15 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:插件防不住的资源消耗
WordPress 安全插件(Wordfence、Cerber)确实能挡暴力破解,但它们工作在 PHP 层——请求已经进入了 WordPress。这意味着:
- 每次尝试都要加载 WordPress 核心,消耗 CPU 和内存;
- 高并发扫描时,插件本身可能成为瓶颈;
- 插件被禁用或未安装时完全没有防护。
Fail2ban 工作在操作系统层:它扫描日志文件,发现异常模式就用 iptables/nftables 直接封掉 IP,后续请求连 Nginx 都到不了。资源消耗几乎为零。
原理分析:Fail2ban 的三个组成部分
- Filter(过滤器):用正则匹配日志里的”失败事件”,比如”密码错误”、”404 扫描”;
- Jail(监狱):把 filter 和动作绑定,定义”多久内失败几次就封多久”;
- Action(动作):封禁手段,通常是
iptables/nftables/firewalld。
工作流程:
日志新增一行 → filter 正则匹配 → 计数器 +1
→ 达到 maxretry → 执行 action(封 IP bantime 秒)
→ bantime 到期 → 自动解封
和插件的分工:Fail2ban 防的是”频次型攻击”(大量失败尝试),插件防的是”内容型攻击”(SQL 注入、文件上传)。两者互补,不是替代。
保姆级实操
第一步:安装
# Debian / Ubuntu
sudo apt install fail2ban -y
# CentOS / Rocky / AlmaLinux
sudo dnf install epel-release -y
sudo dnf install fail2ban fail2ban-firewalld -y
sudo systemctl enable --now fail2ban
第二步:先保护 SSH(必做)
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
编辑 /etc/fail2ban/jail.local:
[DEFAULT]
# 白名单:把自己的 IP 和内网加进去,防误封
ignoreip = 127.0.0.1/8 ::1 1.2.3.4 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
bantime = 3600 # 封 1 小时
findtime = 600 # 10 分钟内的统计窗口
maxretry = 5 # 失败 5 次触发
backend = systemd # 或 auto
destemail = you@example.com
sender = fail2ban@mimangfei.com
action = %(action_mwl)s # 封禁 + 发邮件带日志
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
maxretry = 3
bantime = 86400 # SSH 失败 3 次封一天
第三步:写 WordPress 专用 filter
创建 /etc/fail2ban/filter.d/wordpress-auth.conf:
[Definition]
# 匹配 wp-login.php 的 POST 请求(登录尝试)
failregex = ^<HOST> .* "POST /wp-login\.php
^<HOST> .* "POST /wp-admin/admin-ajax\.php.*action=login
^<HOST> .* "GET /wp-login\.php\?action=(register|lostpassword)
ignoreregex =
注意:这个 filter 匹配的是”任何 POST wp-login.php”,包括你自己成功登录。所以 maxretry 要设宽松一点(比如 10 次 / 10 分钟),否则输错几次密码把自己封了。
更精准的方案:只匹配失败的登录
WordPress 默认日志不记录登录失败(需要插件)。两个办法:
办法 A:装 WP Fail2Ban Redux 插件,它会把登录失败写入 syslog,然后:
# /etc/fail2ban/filter.d/wordpress-hard.conf
[Definition]
failregex = ^%(__prefix_line)sAuthentication failure for .* from <HOST>$
^%(__prefix_line)sBlocked authentication attempt for .* from <HOST>$
^%(__prefix_line)sXML-RPC authentication failure from <HOST>$
ignoreregex =
# jail.local
[wordpress-hard]
enabled = true
filter = wordpress-hard
logpath = /var/log/auth.log # Debian/Ubuntu
# logpath = /var/log/secure # CentOS
maxretry = 3
bantime = 86400
port = http,https
[wordpress-soft]
enabled = true
filter = wordpress-soft
logpath = /var/log/auth.log
maxretry = 5
bantime = 3600
port = http,https
办法 B:用 Nginx 状态码匹配。给失败的 wp-login 请求打标记比较麻烦,退而求其次——匹配高频 404 / 403:
# /etc/fail2ban/filter.d/nginx-wp-scan.conf
[Definition]
failregex = ^<HOST> .* "(GET|POST|HEAD) /(wp-login|xmlrpc|wp-config|\.env|\.git|phpmyadmin|adminer|setup-config)
ignoreregex = ^<HOST> .* "(GET|POST) /wp-login\.php.* 200
这个 filter 能抓到绝大多数自动化扫描器(它们会探测 wp-config.php.bak、.env、xmlrpc.php 等)。
第四步:配置 jail
在 /etc/fail2ban/jail.local 追加:
[nginx-wp-login]
enabled = true
filter = wordpress-auth
logpath = /var/log/nginx/access.log
maxretry = 10
findtime = 600
bantime = 3600
port = http,https
action = iptables-multiport[name=wp, port="http,https", protocol=tcp]
[nginx-wp-scan]
enabled = true
filter = nginx-wp-scan
logpath = /var/log/nginx/access.log
maxretry = 3
findtime = 300
bantime = 86400
port = http,https
第五步:重载并验证
sudo fail2ban-client reload
sudo fail2ban-client status
# 应看到 jail 列表
sudo fail2ban-client status nginx-wp-login
# 看 Currently banned / Total banned
测试 filter 是否匹配(重要):
# 用真实日志测试正则
sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress-auth.conf
输出会显示匹配了多少行。如果 0 行,说明正则和你的日志格式不符,必须调。
第六步:配置邮件告警(可选)
[DEFAULT]
destemail = admin@mimangfei.com
sender = fail2ban@mimangfei.com
mta = sendmail
action = %(action_mwl)s
邮件发送需要服务器能发信,参考WordPress SMTP 邮件配置里的方法配置好 MTA。
第七步:常用运维命令
# 查看某 jail 状态
sudo fail2ban-client status nginx-wp-login
# 手动解封
sudo fail2ban-client set nginx-wp-login unbanip 1.2.3.4
# 手动封禁
sudo fail2ban-client set nginx-wp-login banip 1.2.3.4
# 查看 iptables 规则
sudo iptables -L f2b-nginx-wp-login -n -v
常见踩坑
- 把自己封了:
ignoreip没加自己的 IP。自救办法:从另一台机器(或手机 4G)SSH 上去执行fail2ban-client unbanip,或者直接改ignoreip后 reload。 - 日志格式不匹配:Nginx 的
log_format用了combined还是main,IP 是$remote_addr还是$http_x_forwarded_for,决定<HOST>能不能匹配到。用fail2ban-regex验证,不要凭感觉。 - CDN 下封错 IP:如果前置 Cloudflare,
$remote_addr是 CDN 节点 IP,封 IP 等于封掉 CDN,全站不可访问。必须先在 Nginx 里用real_ip还原真实 IP,并把日志格式改成记录还原后的 IP,参考反向代理下获取真实 IP。 - 案例:某站
/var/log/nginx/access.log被 logrotate 每天切割,Fail2ban 持有的文件句柄指向已被删除的旧文件,导致监控失效却不报错。解决:Fail2ban 的backend设为auto或polling,或确保 logrotate 用copytruncate并配合postrotate重载 fail2ban。 - 封禁不生效:检查 action 用的是
iptables还是nftables。Debian 11+/Ubuntu 22.04+ 默认 nftables,如果 Fail2ban 配的是 iptables 会不生效。查看:
bash
sudo fail2ban-client status nginx-wp-login | grep -i "action"
现代系统建议改用 action = nftables-multiport[name=wp, port="http,https"]。
- Docker 环境:Docker 自己写 iptables 规则,可能和 Fail2ban 冲突(Fail2ban 的链顺序在 Docker 之后,封不住)。这种场景建议在上层反向代理做限流(Nginx
limit_req)。 - Nginx 限流作为补充:Fail2ban 是事后封禁,Nginx 的
limit_req是事前限流,两者配合更好:
“`nginx
# http 块
limit_req_zone $binary_remote_addr zone=wplogin:10m rate=10r/m;
# server 块
location = /wp-login.php {
limit_req zone=wplogin burst=5 nodelay;
# … PHP 处理
}
“`
总结
Fail2ban 是服务器层防护的核心工具:零性能开销、自动封禁、自动解封。落地四个要点:ignoreip 先加白名单、filter 正则必须用 fail2ban-regex 实测、CDN 环境先还原真实 IP、现代系统注意 iptables/nftables 的选择。建议至少配三个 jail:sshd(3 次封一天)、wordpress 登录失败(配合 WP Fail2Ban 插件)、扫描探测(匹配 .env / xmlrpc.php / wp-config 等路径,3 次封一天)。配合 限制 wp-admin 访问的 IP 白名单,后台安全性会有质的提升。
