Linux 下自动检测 Nginx 错误日志中的攻击 IP 并自动封禁
当然,这里帮你把刚才的解决方案整理成了一篇结构清晰、可直接发布的博客文章,包含 Fail2ban 和自写脚本两种方式,你可以直接复制使用。
---
# Linux 下自动检测 Nginx 错误日志中的攻击 IP 并自动封禁
Nginx 作为最常用的反向代理和 Web 服务器,每天都会面对大量扫描和攻击请求。虽然这些请求大部分会被应用本身或 WAF 拦截,但在错误日志中仍然会留下大量痕迹,比如:
- 大量对 .php、`.asp`、`.env`、`.git` 等敏感文件的 404 请求
- 异常的 POST 请求体大小(`client intended to send too large body`)
- SSL 握手失败(`SSL_do_handshake() failed`)等
如果这些攻击行为集中在某些 IP 上,我们完全可以**自动识别并加入黑名单**,直接在 Nginx 层面拒绝服务,减轻后端压力。
本文将介绍两种自动化方案:使用 fail2ban 和**自定义 Shell 脚本**,都能实现:
- 实时监控 Nginx 错误日志
- 按规则提取异常攻击 IP
- 自动写入黑名单文件 /mnt/nginx_conf/blacklist.conf
- 自动执行 nginx -t 检测配置,通过后 nginx -s reload
最终效果是,黑名单中的 IP 会被 Nginx 直接返回 403。
---
## 前置准备:统一的黑名单文件
首先创建一个空的黑名单文件,并在 Nginx 主配置中引用:
```bash
touch /mnt/nginx_conf/blacklist.conf
```
编辑 Nginx 主配置文件(通常为 /etc/nginx/nginx.conf),在 http 块中加入:
```nginx
http {
# ... 其他配置
include /mnt/nginx_conf/blacklist.conf;
# ...
}
```
以后黑名单文件中每一行格式如下:
```nginx
deny 1.2.3.4;
```
这样 Nginx 就会拒绝该 IP 的所有请求。
> 注意:文件路径 /mnt/nginx_conf/ 请根据实际情况调整,并确保 Nginx 进程有读取权限。
---
## 方案一:使用 Fail2ban(推荐)
Fail2ban 是专为扫描日志、动态封禁 IP 设计的工具,配置灵活,防误封机制完善。
### 1. 安装 Fail2ban
```bash
# Debian/Ubuntu
sudo apt update && sudo apt install fail2ban -y
# CentOS/RHEL
sudo yum install epel-release -y
sudo yum install fail2ban -y
```
### 2. 创建自定义过滤器
新建文件 /etc/fail2ban/filter.d/nginx-error-attack.conf:
```ini
[Definition]
failregex = ^<HOST> .* "(GET|POST|HEAD) .*(\.php|\.asp|\.aspx|\.env|\.git|wp-admin|admin|config|bak|sql)" .* 404
^<HOST> .* client intended to send too large body
^<HOST> .* SSL_do_handshake\(\) failed
ignoreregex =
```
这里 <HOST> 是 Fail2ban 自动识别的 IP 部分,正则表达式可以根据你的实际日志特征自由调整。
### 3. 配置监狱 (Jail)
编辑 /etc/fail2ban/jail.local(没有则新建),加入:
```ini
[nginx-error-attack]
enabled = true
port = http,https
filter = nginx-error-attack
logpath = /var/log/nginx/error.log
maxretry = 3
findtime = 300
bantime = 3600
action = nginx-blocklist
```
- maxretry:在 findtime 秒内达到该次数即封禁
- bantime:封禁时间(秒),这里设为 1 小时
- action:调用我们自定义的动作 nginx-blocklist
### 4. 自定义动作:写入黑名单并重载 Nginx
创建文件 /etc/fail2ban/action.d/nginx-blocklist.conf:
```ini
[Definition]
actionstart =
actionstop =
actioncheck =
actionban = echo "deny <ip>;" >> /mnt/nginx_conf/blacklist.conf
nginx -t && nginx -s reload
actionunban = sed -i "/deny <ip>;/d" /mnt/nginx_conf/blacklist.conf
nginx -t && nginx -s reload
```
当 IP 被封禁时,自动追加 deny <ip>; 到黑名单文件并重载 Nginx;当封禁到期解封时,自动从文件中删除该行并重载。
### 5. 启动并验证
```bash
sudo systemctl enable fail2ban
sudo systemctl restart fail2ban
# 查看监狱状态
sudo fail2ban-client status nginx-error-attack
```
如果一切正常,你会看到监狱已启用,并且之后可以通过日志查看封禁记录。
优点:
- 自带时间窗口、重试次数、自动解封机制
- 稳定可靠,避免重复造轮子
- 易于扩展多个服务
---
## 方案二:自定义 Shell 脚本
如果你不想引入额外的守护进程,也可以用 Shell 脚本配合 cron 定时任务来实现。
### 1. 脚本:`/usr/local/bin/nginx_attack_blocker.sh`
```bash
#!/bin/bash
LOG_FILE="/var/log/nginx/error.log"
BLACKLIST="/mnt/nginx_conf/blacklist.conf"
OFFSET_FILE="/tmp/nginx_error_offset"
MAX_FAILS=5 # 同一IP在同一批次日志中出现次数阈值
FIND_TIME=1800 # 统计时间窗口(秒),此处仅用于说明
LOCK_FILE="/tmp/nginx_blocker.lock"
# 文件锁,防止并发执行
exec 200>"$LOCK_FILE"
flock -n 200 || { echo "Another instance is running"; exit 1; }
# 攻击特征正则(可根据实际情况调整)
ATTACK_PATTERN='(client intended to send too large body|SSL_do_handshake.*failed|"(GET|POST) .*(\.php|\.asp|admin|\.env|\.git|config).*" 404)'
# 读取上次处理的行号
if [[ -f "$OFFSET_FILE" ]]; then
LAST_LINE=$(cat "$OFFSET_FILE")
else
LAST_LINE=0
fi
CUR_LINE=$(wc -l < "$LOG_FILE")
if [[ $CUR_LINE -le $LAST_LINE ]]; then
exit 0
fi
# 提取新增日志中的攻击IP并计数
NEW_IPS=$(tail -n +$((LAST_LINE+1)) "$LOG_FILE" \
| grep -iP "$ATTACK_PATTERN" \
| grep -oP '^\S+' \
| sort | uniq -c | sort -rn)
BLOCKED=0
while read count ip; do
[[ -z "$ip" ]] && continue
# 已封禁IP跳过
if grep -q "deny $ip;" "$BLACKLIST"; then
continue
fi
if [[ $count -ge $MAX_FAILS ]]; then
echo "deny $ip; # $(date '+%Y-%m-%d %H:%M:%S')" >> "$BLACKLIST"
echo "Blocked $ip (fail count: $count)"
BLOCKED=1
fi
done <<< "$NEW_IPS"
# 如果封禁了新IP,测试并重载Nginx
if [[ $BLOCKED -eq 1 ]]; then
if nginx -t &>/dev/null; then
nginx -s reload
echo "Nginx reloaded successfully"
else
echo "Nginx config test failed! Please check blacklist file manually."
# 可在此加入回滚逻辑
fi
fi
# 记录本次处理行号
echo "$CUR_LINE" > "$OFFSET_FILE"
```
赋予执行权限:
```bash
chmod +x /usr/local/bin/nginx_attack_blocker.sh
```
### 2. 添加定时任务
每 2 分钟执行一次:
```bash
sudo crontab -e
```
添加:
```cron
*/2 * * * * /bin/bash /usr/local/bin/nginx_attack_blocker.sh >> /var/log/nginx_blocker.log 2>&1
```
### 3. 处理日志轮转
Nginx 日志通常由 logrotate 管理,当日志被切割后,偏移量会失效。需要在 logrotate 的 postrotate 中重置偏移文件。
编辑 /etc/logrotate.d/nginx,在 postrotate 段落中加入:
```bash
postrotate
# ... 原有内容
echo 0 > /tmp/nginx_error_offset
endscript
```
优点:
- 无需安装额外软件,纯 Shell 实现
- 灵活可控,适合定制化需求
缺点:
- 没有自动解封机制(需自行扩展)
- 无防误封的细粒度时间窗口控制(当前仅按单次扫描次数判断)
- 需要额外处理日志轮转
---
## 两种方案对比
| 特性 | Fail2ban | 自定义脚本 |
|--------------------|-----------------------------------|----------------------------------|
| 安装复杂度 | 需安装 fail2ban | 仅需 Shell,无需额外依赖 |
| 自动解封 | 支持 | 默认不支持(需自行添加) |
| 防误封机制 | 时间窗口 + 重试次数 | 仅按批次累计次数,较简陋 |
| 稳定性与维护成本 | 成熟项目,社区支持 | 自行维护,可能遗漏边界情况 |
| 资源占用 | 轻量守护进程 | 定时任务,几乎无额外占用 |
建议:生产环境推荐使用 Fail2ban;个人测试或极简环境可考虑脚本方案,但需注意风险。
---
## 注意事项
1. 日志路径:请根据你的 Nginx 编译参数和系统确认错误日志路径,常见为 /var/log/nginx/error.log,OpenResty 等可能不同。
2. 权限问题:无论是 Fail2ban 还是脚本,都需要有权限写入黑名单文件并执行 nginx -t / nginx -s reload。建议用 root 用户执行,或配置 sudo 无密码。
3. 正则匹配:提供的 failregex 和脚本正则仅为示例,请务必根据你日志中实际攻击特征进行调整,以免误杀正常请求或漏报。
4. 白名单:如果有可信 IP(如办公网络、CDN 回源 IP),应在 Nginx 配置中**先于黑名单 include** 设置 allow,避免自己被封。
5. 监控告警:建议对黑名单文件的变更和重载失败情况加入简单的监控或日志记录,以便及时发现问题。
-