你的服务器每天被扫多少次
新装一台 VPS,只要 22 端口开着,平均几小时内就会开始收到 SSH 爆破尝试。去 /var/log/auth.log 看一眼就知道,一天几千条 Failed password 是常态——这些不是针对你的,是全球扫描器在无差别扫全网 IP 段。
很多人以为"我用密钥登录、关了密码登录就安全了",这没错,但还有三个现实问题:
- 爆破尝试本身消耗 CPU、把日志盘写满、让
auth.log滚得飞快,你想查真问题都翻不出来; - 网站后台(
/admin/、wp-login.php)是明文密码登录,爆破成功率远高于你想象的——尤其是弱口令; - 你有多个服务暴露在外(SSH、FTP、数据库、面板),逐个加固容易漏。
fail2ban 的思路很朴素:读日志 → 匹配特征 → 用防火墙临时封 IP。比起改端口、装 WAF,它是性价比最高的一层防护,配置一次管很久。
一、安装与基本概念
apt update && apt install -y fail2ban
systemctl enable --now fail2ban
fail2ban-client status三个必须搞懂的概念:
- jail(监狱):一个监控单元,绑定"一个日志文件 + 一套匹配规则 + 一个动作";
- filter:正则规则,定义"什么样的日志行算攻击";
- action:匹配到之后干什么,通常是调 iptables 封 IP。
默认配置在 /etc/fail2ban/jail.conf,但绝对不要直接改它——软件升级会覆盖。正确做法是在 /etc/fail2ban/jail.local 里写覆盖项,这个文件不会被升级动到。
二、全局参数:先调好这几个
新建 /etc/fail2ban/jail.local:
[DEFAULT]
# 封禁时长(秒);-1 表示永久
bantime = 3600
# 统计窗口(秒)
findtime = 600
# 窗口内失败几次就封
maxretry = 5
# 忽略这些地址(务必加!)
ignoreip = 127.0.0.1/8 ::1 你的固定IP
# 用 nftables 或 iptables 都行,新系统推荐 nftables
banaction = nftables-multiport
banaction_allports = nftables-allports
# 封禁后发邮件通知(可选)
# destemail = you@example.com
# action = %(action_mwl)s关于 ignoreip:这是最容易把自己锁在门外的参数。如果你用动态 IP 办公/在家,至少把当前 IP 加进去;更好的做法是给服务器留一条"带外通道"(比如云厂商的 VNC 控制台),万一被封能进去。
关于 bantime:建议从 1 小时起步,别一上来就 -1(永久)。永久封禁看起来爽,但一旦误封自己的动态 IP,就只能走控制台了。3600 秒已经能让爆破脚本放弃,因为它们通常只尝试几分钟。如果发现某些 IP 反复来,再针对性地加长。
改完 systemctl restart fail2ban。
三、Jail 一:SSH 防护(标配)
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = systemd
maxretry = 3
bantime = 7200说明:Debian 12 / Ubuntu 22+ 的 SSH 日志走 systemd journal,所以要写 backend = systemd,logpath 用变量即可。如果系统是老的日志文件模式(/var/log/auth.log),把 backend 去掉、logpath 写成实际路径。
SSH 可以设得比全局更严:密码登录本就该关掉,任何一次失败都很可疑,所以 maxretry = 3、bantime = 7200 很合理。
验证是否生效:
fail2ban-client status sshd输出里会列 Currently banned 和 Total banned。如果你刚装完就发现已经封了好几个 IP,说明你的服务器确实一直在被扫。
四、Jail 二:网站后台登录防护(个人站重点)
SSH 有密钥兜底,网站后台才是真正的软肋。以 Nginx + Typecho/WordPress 为例,登录失败一般会返回 401 或 403,或者日志里有特定的 POST /admin/login.php 记录。
先用一个自定义 filter。新建 /etc/fail2ban/filter.d/nginx-login.conf:
[Definition]
failregex = ^<HOST> - .* "(POST|GET) /(admin|wp-login\.php|administrator)[^"]*" (401|403)
ignoreregex =注意 <HOST> 是 fail2ban 的占位符,用来提取要封的 IP;正则里的反斜杠要写两个。这条规则的含义是:请求后台登录路径且返回 401/403,计为一次失败。
然后在 jail.local 里加:
[nginx-login]
enabled = true
port = http,https
filter = nginx-login
logpath = /var/log/nginx/access.log
maxretry = 5
findtime = 300
bantime = 86400这里 bantime 设成一天比较合适——能坚持爆破后台的,基本可以判定是恶意流量,封久一点不心疼。
关键前提:Nginx 日志里必须记录真实客户端 IP。如果你在 Nginx 前面套了 CDN(Cloudflare 等),默认 $remote_addr 记的是 CDN 节点 IP,那么 fail2ban 会把整个 CDN 段封掉,全站直接挂。必须两步处理:
第一步,在 Nginx 里用 real_ip 模块还原真实 IP(以 Cloudflare 为例):
# 下载 CF 的 IP 段,生成 real_ip 配置
# 简化示例(务必用官方最新段 /etc/nginx/cf_ips.conf)
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
# ... 其余 CF 段
real_ip_header CF-Connecting-IP;
real_ip_recursive on;第二步,在 log_format 里确保用 $remote_addr(经 real_ip 还原后即为真实 IP),不要用 $http_x_forwarded_for 直接取(可被伪造,会导致 fail2ban 封错人)。
这个坑非常致命:"fail2ban 把 Cloudflare 的 IP 封了导致全站 502"是很常见的生产事故。上 CDN 的站,配 fail2ban 前一定要确认这两点。
五、Jail 三:通用 404 扫描封禁(可选,效果意外地好)
恶意扫描器会疯狂请求 /.env、/wp-admin/、/phpmyadmin/、/.git/config 这些不存在的路径。虽然它们拿到 404 就跑了,但量很大。用"短时间内大量 404"作为特征封禁,能一次性干掉一整类扫描器:
[nginx-404]
enabled = true
port = http,https
filter = nginx-404
logpath = /var/log/nginx/access.log
maxretry = 30
findtime = 60
bantime = 3600配套 filter filter.d/nginx-404.conf:
[Definition]
failregex = ^<HOST> - .* "GET [^"]*" 404
ignoreregex =阈值设 30 次/分钟,正常用户不会触发,扫描器必中。注意:如果你的站本身有大量真实死链,先修死链再开这个 jail,否则可能误封正常访问者。
六、运维命令与排错
日常最常用的几条:
# 查看所有 jail 状态
fail2ban-client status
# 看某个 jail 详情(含当前封禁的 IP 列表)
fail2ban-client status sshd
# 手动封禁 / 解封一个 IP
fail2ban-client set sshd banip 1.2.3.4
fail2ban-client set sshd unbanip 1.2.3.4
# 测试正则是否匹配你的日志(改 filter 后必做)
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-404.conf
# 重载配置
fail2ban-client reloadfail2ban-regex 是配 filter 时唯一的救命稻草。写完正则先用它验证,能省掉大量"改了配置但就是不生效"的瞎猜时间。它会输出匹配行数和示例,如果匹配数远小于你预期,说明正则写错了。
常见问题:
- jail 启动失败:多半是 filter 正则语法错或 logpath 不存在。看
journalctl -u fail2ban -n 50。 - 显示已封禁但 IP 还能访问:banaction 选错了(nftables vs iptables 与系统实际不符)。用
nft list ruleset或iptables -L -n确认规则是否真的被写入。 - 本机被自己封了:立刻用云控制台 VNC 登入
fail2ban-client set sshd unbanip 你的IP,并把 IP 加进ignoreip。 - 封禁列表突然暴涨:可能是你的正则太宽泛(比如把正常 404 也算进去),先
fail2ban-regex检查,必要时放宽阈值或收窄正则。
七、fail2ban 不是万能的
必须说清楚它的边界,避免产生虚假的安全感:
- 它防不住分布式爆破。攻击者用几千个 IP 每个只试两次,fail2ban 的 maxretry 根本触发不到。对付这种要靠密钥登录 + 强口令 + 二次验证(TOTP),而不是封禁。
- 它防不住应用层漏洞。SQL 注入、文件上传漏洞不会表现为"登录失败",fail2ban 看不到。
- 封禁是临时的。bantime 到期自动解封,恶意 IP 会卷土重来。这没问题,因为你的目标是把成本抬高,而不是彻底消灭。
正确的定位是:fail2ban 是性价比最高的一层"减噪 + 拖延"防护。真正的基本盘还是那几条老规矩——SSH 只用密钥、关掉密码登录、后台不暴露在公网(能绑内网或加一层 Basic Auth 就更好)、数据库不对公网开放。先把这些做完,再上 fail2ban 收尾。
对个人站长来说,一个下午的配置换来日志清净、服务器负载下降、后台被爆破的心理压力消失,这笔投入非常划算。配好后建议每周花一分钟看一眼 fail2ban-client status,如果 Total banned 在持续增长,说明你的服务器正处在被持续扫描中——这正是它存在的意义。