首页 / 帮助文档 / Ubuntu上配置Fail2ban过滤Nginx异常访问日志

Ubuntu上配置Fail2ban过滤Nginx异常访问日志

Nginx的访问日志里藏着大量异常流量,从恶意爬虫、漏洞扫描到暴力破解,这些行为不仅消耗服务器资源,还可能带来安全风险。直接用iptables手动封禁效率太低,Fail2ban正好能自动化这个流程。但很多人在配置时卡在过滤规则上——要么正则写得不精准误伤正常用户,要么压根匹配不到该封的IP。这篇文章直接解决这个问题,从日志格式分析、过滤器编写到jail配置,一步步把Nginx异常访问的自动封禁跑通。

先搞清楚Nginx日志格式

Fail2ban过滤日志的前提是日志格式可解析。Nginx默认的combined格式长这样:

log_format combined '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

如果你自定义过日志格式,务必确认字段顺序。因为Fail2ban的过滤器完全依赖正则表达式去匹配,字段位置一变,正则就得跟着改。建议在配置Fail2ban之前,先到Nginx配置目录确认当前站点实际使用的log_format,然后在Fail2ban的过滤器里对应调整。很多人忽略了这一步,直接用网上抄的规则,结果封禁列表永远是空的。

安装Fail2ban并了解基本结构

Ubuntu上安装很简单:

sudo apt update
sudo apt install fail2ban -y

安装完成后,Fail2ban的配置文件分布在两个目录:/etc/fail2ban/jail.conf是默认的jail配置文件,/etc/fail2ban/filter.d/存放各种服务的过滤规则。注意不要直接修改jail.conf,正确的做法是创建jail.local文件,Fail2ban会优先读取.local后缀的配置,这样系统更新时不会覆盖你的自定义设置。

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

Fail2ban的工作逻辑很清晰:监控指定日志文件,用过滤器中的正则表达式匹配每一行日志,匹配成功就触发动作(通常是调用iptables封禁IP),封禁持续一段时间后自动解封。理解这个流程有助于后续排查问题。

分析Nginx日志中需要封禁的异常行为

在写过滤规则之前,先明确要拦截什么。常见的Nginx异常访问包括:

1. 频繁404错误——通常是扫描器在探测敏感路径,比如/wp-admin、/.env、/phpmyadmin这类。

2. 频繁403错误——可能是尝试访问被deny的目录或文件。

3. 对登录接口的暴力请求——比如反复POST到/wp-login.php或自定义的登录地址,返回401或特定状态码。

4. 恶意User-Agent——一些自动化工具的特征很明显,比如sqlmap、nmap、各类漏洞扫描器。

5. 请求频率异常——单个IP在短时间内发起大量请求,远超正常浏览行为。

针对这些场景,我们需要创建多个过滤器,每个过滤器专注于一类异常,这样封禁策略可以灵活调整。

创建第一个过滤器:拦截404扫描行为

在/etc/fail2ban/filter.d/目录下新建一个文件,命名为nginx-404.conf:

sudo nano /etc/fail2ban/filter.d/nginx-404.conf

内容如下:

[Definition]
failregex = ^<HOST> -.*"(GET|POST|HEAD).*" 404 .*$
ignoreregex =

这条正则的意思是:匹配以IP地址开头,后面跟着请求方法,最终返回404状态码的日志行。<HOST>是Fail2ban内置的变量,会自动匹配IPv4和IPv6地址。这里有几个细节要注意:

第一,正则里的^锚定行首,确保从日志开头开始匹配,避免误匹配到referer字段里的URL。

第二,.*用于跳过不关心的字段,但如果你需要更精确的匹配(比如只拦截对.php文件的404请求),可以写成:

failregex = ^<HOST> -.*"(GET|POST|HEAD).*\.php.*" 404 .*$

这样只会封禁扫描PHP文件的IP,对普通用户误点死链的情况更宽容。

测试过滤器是否正确匹配,用fail2ban-regex命令:

sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-404.conf

这个命令会输出匹配到的行数和具体内容,确认无误后再进行下一步。如果匹配数为零,检查日志路径和正则表达式,这是最常见的两个坑。

创建第二个过滤器:拦截恶意User-Agent

很多自动化攻击工具的User-Agent带有明显特征,可以直接封禁。创建nginx-badbots.conf:

sudo nano /etc/fail2ban/filter.d/nginx-badbots.conf

内容:

[Definition]
failregex = ^<HOST> -.*"(GET|POST|HEAD).*" \d+ \d+ ".*" "(?i)(sqlmap|nikto|acunetix|nessus|nmap|openvas|w3af|burpsuite|zgrab|masscan|gobuster|dirbuster|feroxbuster).*"$
ignoreregex =

(?i)表示不区分大小写,后面用竖线分隔各个恶意工具的特征名。你可以根据实际遇到的攻击工具持续补充这个列表。注意这条正则要求日志格式中包含User-Agent字段,也就是combined格式的最后一个双引号内容。如果你的日志格式简化过,需要相应调整正则的字段位置。

创建第三个过滤器:拦截对敏感路径的访问

有些路径正常用户永远不会访问,一旦有人请求就高度可疑。创建nginx-sensitive.conf:

sudo nano /etc/fail2ban/filter.d/nginx-sensitive.conf

内容:

[Definition]
failregex = ^<HOST> -.*"(GET|POST|HEAD) (/\.env|/\.git|/wp-admin|/phpmyadmin|/adminer|/actuator|/swagger|/api-docs|/druid|/console|/.DS_Store).*" .*$
ignoreregex =

这里列出的路径只是示例,你应该根据自己应用的特点来定制。比如你的站点根本没有WordPress,那/wp-admin的访问就一定是扫描行为。反过来,如果你的站点确实用了这些路径,就不要把它们放进敏感列表,否则会封禁正常用户。

创建第四个过滤器:拦截暴力登录尝试

这个场景稍微复杂一点,因为需要结合请求方法、URL和返回状态码来判断。假设你的应用登录接口是/api/login,登录失败返回401状态码:

sudo nano /etc/fail2ban/filter.d/nginx-login.conf

内容:

[Definition]
failregex = ^<HOST> -.*"POST /api/login.*" 401 .*$
ignoreregex =

如果你的应用登录失败返回的是200状态码但响应体里包含"login failed"之类的字符串,那Nginx的access.log里不会记录响应体内容,这种情况需要在应用层记录专门的认证日志,然后用Fail2ban监控那个日志文件。不要试图在Nginx层面解决所有问题,分层防御才是正确的思路。

配置jail.local启用过滤器

过滤器写好了,接下来在jail.local里启用它们。编辑/etc/fail2ban/jail.local:

sudo nano /etc/fail2ban/jail.local

在文件末尾添加以下内容:

[nginx-404]
enabled = true
port = http,https
filter = nginx-404
logpath = /var/log/nginx/access.log
maxretry = 5
findtime = 60
bantime = 3600

[nginx-badbots]
enabled = true
port = http,https
filter = nginx-badbots
logpath = /var/log/nginx/access.log
maxretry = 1
findtime = 300
bantime = 86400

[nginx-sensitive]
enabled = true
port = http,https
filter = nginx-sensitive
logpath = /var/log/nginx/access.log
maxretry = 1
findtime = 300
bantime = 86400

[nginx-login]
enabled = true
port = http,https
filter = nginx-login
logpath = /var/log/nginx/access.log
maxretry = 10
findtime = 120
bantime = 7200

每个jail的参数含义:

enabled:设为true启用这个jail。

port:封禁时在iptables规则中限制的端口,http,https对应80和443。

filter:指定使用filter.d目录下的哪个过滤器文件,不需要写.conf后缀。

logpath:要监控的日志文件路径,如果有多个站点可以写多条,用换行加空格缩进的方式列出。

maxretry:在findtime时间内允许的最大匹配次数,超过就封禁。

findtime:统计时间窗口,单位秒。

bantime:封禁时长,单位秒。3600是一小时,86400是一天。

这里有几个策略上的考量:对于404扫描,maxretry设为5、findtime设为60秒,意味着60秒内出现5次404就封禁一小时,这个阈值相对宽松,避免误伤正常用户点错链接。而对于恶意User-Agent和敏感路径访问,maxretry直接设为1,因为正常用户不会带着sqlmap的User-Agent来访问,也不会去请求/.env文件,一次命中即可封禁,bantime设为一整天。登录暴力破解的阈值则根据用户体验来调,120秒内10次失败比较合理。

处理多站点和日志轮转

如果你的服务器上运行多个Nginx站点,每个站点有独立的日志文件,可以用通配符或者列出多个路径:

logpath = /var/log/nginx/site1-access.log
          /var/log/nginx/site2-access.log
          /var/log/nginx/site3-access.log

日志轮转是另一个容易忽略的问题。Nginx的日志通常由logrotate管理,每天或每周切割一次,旧文件被重命名。Fail2ban默认监控的是原始日志文件路径,轮转后文件被移走,Fail2ban会自动跟踪新的文件,但前提是使用了inotify机制。如果发现日志轮转后Fail2ban停止工作,检查logrotate配置是否使用了copytruncate模式,这种模式会截断文件而不是移动文件,Fail2ban能正常处理。如果是create模式,需要确保Fail2ban的后端设为auto或pyinotify。

验证和调试

配置完成后重启Fail2ban:

sudo systemctl restart fail2ban

查看各个jail的运行状态:

sudo fail2ban-client status
sudo fail2ban-client status nginx-404

第二条命令会显示具体某个jail的封禁数量和当前封禁的IP列表。如果发现封禁数量异常多或者为零,就需要排查:

1. 检查日志路径是否正确,Fail2ban是否有读取权限。

2. 用fail2ban-regex测试过滤器是否匹配到预期的日志行。

3. 查看/var/log/fail2ban.log,Fail2ban自身的日志会记录匹配和封禁的详细信息。

4. 确认iptables规则是否生效:sudo iptables -L -n | grep f2b。

一个常见的坑是时区问题。Nginx日志里的时间戳和Fail2ban处理的时间可能存在偏差,导致findtime计算不准。如果日志和系统时区不一致,在jail.local里可以用timezone参数指定时区,不过通常保持系统时区统一即可避免这个问题。

进阶技巧:自定义动作和忽略IP

默认的封禁动作是iptables-multiport,你也可以换成其他动作,比如发送告警邮件或者调用Cloudflare API封禁。在jail.local的jail段落里添加action参数即可覆盖默认动作。

对于需要排除的IP,比如公司出口IP、搜索引擎爬虫IP,在/etc/fail2ban/jail.local里找到[DEFAULT]段落,添加ignoreip:

[DEFAULT]
ignoreip = 127.0.0.1/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 你的公网IP

多个IP用空格分隔,支持CIDR格式。注意不要把搜索引擎爬虫的IP全部加白,因为攻击者可以伪造User-Agent冒充爬虫。更安全的做法是在Nginx层面用ngx_http_realip_module或CDN的IP头来获取真实IP,确保Fail2ban封禁的是真实客户端地址而非CDN节点IP。

持续优化过滤规则

安全策略不是一劳永逸的。定期检查Fail2ban的封禁日志,分析被封IP的请求模式,如果发现新的攻击特征就补充到过滤器里。同时关注误封情况,如果有正常用户反馈被拦截,检查对应的日志行,调整正则表达式或放宽maxretry阈值。

另外建议对不同的jail设置不同的bantime递进策略。比如第一次封禁一小时,解封后再次触发就封禁一天,第三次封禁一周。Fail2ban默认不支持递进封禁,但可以通过bantime.increment、bantime.factor等参数实现,在jail.local里添加:

bantime.increment = true
bantime.factor = 2
bantime.formula = ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * bantime.factor

这样重复犯事的IP会被关得更久,对顽固的扫描器效果显著。

整套配置跑通之后,Nginx的异常访问就能被自动识别和封禁,服务器的安全水位明显提升。关键是理解每一步在做什么,这样遇到问题时能快速定位,而不是盲目复制粘贴。日志分析和正则表达式是Fail2ban的核心能力,花时间打磨这两项技能,比安装十个安全插件都管用。