#!/bin/bash # 防止上一次任务未结束时重复运行 exec 9>/tmp/nginx-auto-ban.lock flock -n 9 || exit 0 # ================================================== # 可以修改的设置 # ================================================== # 两分钟内访问达到多少次就封禁 THRESHOLD=50 # 如果你有固定管理IP,可以填写 # 没有固定IP就保持为空 ADMIN_IP="" # 公共封禁文件 BAN_FILE="/www/server/nginx/conf/blockips.conf" # Nginx程序 NGINX="/www/server/nginx/sbin/nginx" # ================================================== # 以下内容通常不需要修改 # ================================================== # 当前分钟和上一分钟 NOW=$(LC_ALL=C date '+%d/%b/%Y:%H:%M') PREV=$(LC_ALL=C date -d '1 minute ago' '+%d/%b/%Y:%H:%M') # 临时文件 OLD_FILE="/tmp/nginx-blockips-old.$$" NEW_FILE="/tmp/nginx-blockips-new.$$" IP_FILE="/tmp/nginx-high-frequency-ips.$$" # 任务结束后删除临时文件 trap 'rm -f "$OLD_FILE" "$NEW_FILE" "$IP_FILE"' EXIT # 确保公共封禁文件存在 touch "$BAN_FILE" # 备份修改前的封禁文件 cp -a "$BAN_FILE" "$OLD_FILE" cp -a "$BAN_FILE" "$NEW_FILE" # ================================================== # 读取所有网站最近两分钟的访问日志 # ================================================== for FILE in /www/wwwlogs/*.log do [ -f "$FILE" ] || continue # 排除错误日志和系统日志 case "$FILE" in *.error.log|*/nginx_error.log|*/tcp-access.log|*/tcp-error.log) continue ;; esac # 每个网站只读取最后10000行 # 只统计当前分钟和上一分钟 tail -n 5000 "$FILE" 2>/dev/null | awk -v now="$NOW" -v prev="$PREV" ' { # 只统计当前分钟和上一分钟 if (index($0, "[" now) == 0 && index($0, "[" prev) == 0) { next } # Nginx标准日志中的第7列是请求地址 uri = tolower($7) # 删除问号后面的查询参数 # 例如:app.js?v=123 变成 app.js sub(/\?.*/, "", uri) # 资源文件不参与IP访问频率统计 if (uri ~ /\.(js|css|map|jpg|jpeg|png|gif|webp|bmp|svg|ico|swf|woff|woff2|ttf|eot|otf|mp3|wav|ogg|mp4|webm|avi|mov)$/) { next } # 只输出有效IPv4 if ($1 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) { print $1 } } ' done | sort | uniq -c | awk -v threshold="$THRESHOLD" ' $1 >= threshold { print $2 } ' > "$IP_FILE" CHANGED=0 # ================================================== # 把达到条件的IP加入公共封禁文件 # ================================================== while read -r IP do [ -n "$IP" ] || continue # 不封禁服务器本地地址 case "$IP" in 127.*) continue ;; esac # 不封禁管理员固定IP if [ -n "$ADMIN_IP" ] && [ "$IP" = "$ADMIN_IP" ] then continue fi RULE="deny $IP;" # 已经封禁的IP不重复添加 if ! grep -qFx "$RULE" "$NEW_FILE" then echo "$RULE" >> "$NEW_FILE" echo "$(date '+%F %T') 准备封禁IP:$IP" CHANGED=1 fi done < "$IP_FILE" # 没有新IP,不需要修改Nginx if [ "$CHANGED" -eq 0 ] then echo "$(date '+%F %T') 检查完成,没有发现新的高频IP" exit 0 fi # 使用新的封禁文件 cp -a "$NEW_FILE" "$BAN_FILE" # 检测完整Nginx配置 if "$NGINX" -t then # 检测成功,平滑重载 "$NGINX" -s reload echo "$(date '+%F %T') 新IP已经封禁,Nginx重载成功" else # 检测失败,恢复修改前的文件 cp -a "$OLD_FILE" "$BAN_FILE" echo "$(date '+%F %T') Nginx检测失败,已经恢复旧文件" exit 1 fi