ngx_http_geo_module 不支持运行时更新,但可配合外部脚本实现近似动态黑名单;其基于前缀树优化IP匹配,支持CIDR、IP段和单IP,查询高效且无额外依赖。

用 ngx_http_geo_module 实现“动态黑名单”并不准确——它本身不支持运行时更新,但可作为轻量、高效的基础方案,配合外部脚本实现近似动态效果。核心思路是:用 geo 指令定义 IP 到变量的映射(如 $blocked),再在 server 或 location 中用 if + return 403 拦截。
为什么 geo 比 map 或 lua 更适合封禁场景
geo 模块专为 IP 地址匹配优化,底层使用前缀树(trie),支持 CIDR、IP 段、单 IP,查询复杂度接近 O(1),比 map(哈希表,仅精确匹配)更灵活,也比 lua-resty-iputils 等纯 Lua 方案性能更高、无额外依赖。
- 支持写法:
192.168.1.0/24、2001:db8::/32、1.2.3.4、default 0 - 不支持正则、不支持域名、不支持运行时 reload 后自动生效(需重载 Nginx)
- 变量值只能是字符串或数字,不能调用函数
基础配置:定义封禁列表并拦截
在 http 块中声明:
geo $blocked {
default 0;
192.168.5.100 1;
203.0.113.0/24 1;
2001:db8:1::/64 1;
}在 server 块中拦截:
server {
listen 80;
server_name example.com;
<pre class="brush:php;toolbar:false;">if ($blocked) {
return 403;
}
# 其余配置...}
注意:if 在 server 级别可用,但避免在 location 内嵌套多层 if;若需更精细控制(如仅封 API 路径),可改用 map + return 组合,或升级到 OpenResty 使用 access_by_lua_block。
模拟“动态”:外部脚本 + 平滑重载
所谓动态,本质是定期生成新配置、触发 nginx -s reload。关键点在于原子性与低干扰:
- 将
geo块单独写入/etc/nginx/conf.d/blacklist.conf,不硬编码在主配置里 - 用 Python/Shell 脚本读取威胁情报 CSV 或 Redis 黑名单,生成新的
blacklist.conf - 使用
nginx -t && nginx -s reload,确保语法正确才重载;失败时不中断服务 - 可加锁防止并发写入,或用临时文件 +
mv -f替换保证原子性
增强实用性的小技巧
单纯返回 403 不利于排查,建议补充日志和响应头:
- 在
if ($blocked)前加access_log /var/log/nginx/blocked.log blocked;,并定义log_format blocked '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $request_time'; - 用
add_header X-Blocked "true" always;方便前端或监控识别(注意always确保 403 也生效) - 若需临时放行某 IP(如运维跳板机),可在
geo块顶部加优先级更高的规则:10.10.0.0/16 0;(因 geo 自上而下匹配,首个匹配即终止)
不复杂但容易忽略:geo 变量只在请求初始阶段解析一次,不会被后续 rewrite 或 proxy_pass 影响;只要 IP 匹配就封,不管 Host、URI 或 Header。真正高频变更的场景,建议搭配 Redis + Lua 做实时判断,而 geo 更适合作为兜底层或低频更新的区域封禁方案。


















