nginx 通过 geo + map + if 实现动态 ip 黑名单自动封禁,配合定时脚本分析 access.log 中非法 referer 和静态资源请求,提取高频盗链 ip 写入黑名单文件并重载生效,无需重启、安全高效。

直接通过 Nginx 的 valid_referers 只能做简单 Referer 白名单校验,无法自动封禁盗链 IP。真要实现“定时分析日志 + 自动拉黑”,得靠外部脚本配合 Nginx 的 geo + map + deny 机制,而不是用 ngx_http_access_module 的静态 deny 规则硬写配置文件。
核心思路:用 geo 模块动态加载黑名单
Nginx 不支持运行时 reload 单条 deny 规则,但支持通过 geo 指令从外部文件读取 IP 映射关系,再用 map 转成变量,最后在 server 或 location 块里用 if ($bad_ip) { return 403; } 控制。这样只需重载(不重启)Nginx 就生效,安全又高效。
- 准备一个纯文本黑名单文件,如
/usr/local/nginx/conf/ip_blacklist.conf,每行一个 IP 或 CIDR(如192.168.1.100或203.205.0.0/16) - 在
http块中定义:geo $bad_ip { default 0; include /usr/local/nginx/conf/ip_blacklist.conf; } - 再加一条 map 把数值转为布尔(可选,便于 if 判断):
map $bad_ip $is_blocked { default ""; 1 "1"; } - 在需要防护的 location 中加入:
if ($is_blocked) { return 403; }
怎么从日志识别盗链 IP?关键看 Referer 和资源路径
盗链请求通常满足两个特征:Referer 不在白名单内 + 请求的是图片、CSS、JS 等静态资源。不要只盯 Referer 字段是否为空(空 Referer 可能是直接访问或 HTTPS 跳转),而要匹配你允许的域名(比如只允许 your-site.com 和 www.your-site.com)。
- 用 awk/grep 提取近 24 小时内,Referer 非你域名、且请求后缀为
.jpg.png.webp.css.js的记录 - 统计每个 IP 出现次数,过滤掉高频(比如 ≥ 50 次)或异常 UA(如空 UA + 大量图片请求)
- 排除已知 CDN、爬虫 UA(如 Cloudflare、BaiduSpider),避免误杀
- 示例命令片段:
awk '$NF ~ /\.(jpg|jpeg|png|webp|css|js)$/ && $11 !~ /"-(https?:\/\/)?(www\.)?your-site\.com/' /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | awk '$1 >= 50 {print $2}'
定时脚本:分析 + 去重 + 写入 + 重载
把上面逻辑封装成 shell 脚本(比如 /opt/nginx-block/update_blacklist.sh),用 crontab 每小时跑一次:
- 先备份旧黑名单:
cp /usr/local/nginx/conf/ip_blacklist.conf /usr/local/nginx/conf/ip_blacklist.conf.bak - 生成新 IP 列表(含去重、排除内网、排除已存在条目):
awk ...(上一步命令)... | grep -vE '^(10|172\.(1[6-9]|2[0-9]|3[0-1])|192\.168)\.' | sort -u > /tmp/new_blacklist.tmp
- 合并旧名单(保留人工添加的)和新名单,去重后写入:
cat /usr/local/nginx/conf/ip_blacklist.conf /tmp/new_blacklist.tmp | sort -u | grep -v "^#" > /usr/local/nginx/conf/ip_blacklist.conf
- 语法检查并重载:
nginx -t && nginx -s reload
补充建议:避免误封 & 降低干扰
全自动拉黑容易伤及无辜,尤其对搜索引擎、RSS 阅读器或某些合法聚合站。建议加几道缓冲:
- 新发现 IP 先写入临时观察名单,连续 3 次触发才正式加入黑名单
- 黑名单文件开头加注释说明生成时间,方便人工审计:
# Auto-generated at $(date -Iseconds) - 给被封 IP 返回 403 时,加个简短提示页(用
error_page 403 /403.html),避免用户以为网站挂了 - 定期清理过期黑名单(比如 7 天无请求的 IP 自动移除),可用另一个脚本配合 access.log 时间戳做归档清理
这套方案不依赖第三方模块,纯靠 Nginx 原生能力 + 标准 Linux 工具,稳定、轻量、易维护。关键是把“判断逻辑”交给脚本,“执行控制”交给 Nginx 的 geo 模块,分工清晰,不会因频繁 reload 导致连接中断。











