推荐用geo模块替代线性deny列表,因后者超百条时会顺序扫描、性能下降;geo通过前缀树实现o(1)匹配,支持ipv4/ipv6及cidr,配合include可热更黑名单,再用if+return 403拦截。

用 geo 模块替代线性 deny 列表
当黑名单 IP 超过百条,直接写 dozens 个 deny 指令会导致 Nginx 每次请求都顺序扫描,性能明显下降。推荐改用 geo 模块预计算匹配结果:在 http 块中定义一个变量,把所有需封禁的 IP 映射为 1,其余为 0;再在 server 或 location 中用 if 判断并返回 403。
示例配置:
http {
geo $blocked_ip {
default 0;
203.0.113.5 1;
198.51.100.0/24 1;
2a01:db8::/32 1;
}
}
server {
location / {
if ($blocked_ip) { return 403; }
proxy_pass http://backend;
}
}
拆分规则到外部文件并 include
把大量 IP 条目从主配置中剥离,单独存为 /etc/nginx/conf.d/blocklist.conf,内容只含 IP 和值(如 192.168.5.100 1;),然后在 geo 块里用 include 引入。这样既便于脚本自动更新(比如定时同步威胁情报),也避免主配置臃肿。
注意:include 必须放在 geo 块内部,且文件路径需确保 Nginx 有读取权限;修改后仍需 nginx -t && nginx -s reload 生效。
结合 map 模块做更灵活的标记
如果需要区分封禁类型(如“扫描IP”“爆破IP”“爬虫IP”)并记录不同日志,可用 map 替代 geo。map 支持字符串映射,例如将 IP 映射为标签名,再配合 log_format 输出到独立日志文件,方便后续审计。
示例片段:
map $remote_addr $block_reason {
default "";
203.0.113.5 "brute_force";
198.51.100.12 "scanner";
}
log_format blocked '$remote_addr — $block_reason — $time_local';
避免常见性能陷阱
- 不要在 location / 下堆砌上百行 deny,这是最拖慢请求处理的写法
- 不要对根路径全局启用黑白名单,应限定在 /admin、/login 等高危路径下
- 不建议用 if + $remote_addr 直接做字符串比对,Nginx 的 if 对变量支持有限,易出错且无加速效果
- IPv6 地址要确认编译时启用了 --with-ipv6(现代发行版默认已支持)











