Nginx网关层实现IP黑名单应优先使用geo模块标记IP,再通过map转换状态,最后用return指令拦截;geo支持CIDR且性能优,需确保real_ip配置正确;黑名单可动态更新,推荐include文件 reload或OpenResty+Lua方案。

在Nginx网关层实现IP黑名单,核心是利用geo和map指令预处理IP,再通过deny或return精准拦截,避免直接在location中大量deny导致配置臃肿和性能下降。
用geo模块高效标记黑名单IP
geo模块专为IP匹配优化,支持CIDR、范围和单独IP,且在请求初始化阶段完成判断,性能远高于正则或多次if判断。推荐在http块中定义:
geo $bad_ip {
default 0;
192.168.1.100 1;
203.0.113.0/24 1;
2001:db8::/32 1;
}
注意:geo不支持端口和域名,只处理客户端真实IP(需确保real_ip_header和set_real_ip_from已正确配置以获取前置代理后的源IP)。
结合map转换状态,统一控制拦截动作
将$bad_ip映射为更明确的变量,便于后续逻辑复用(如记录日志、返回不同状态码):
map $bad_ip $blocked {
1 "1";
default "";
}
这样可避免在多个location里重复判断,也方便未来扩展——比如把"1"换成"rate_limited"或"geo_blocked"做精细化区分。
在server或location中执行拦截
推荐使用return而非deny,因return可自定义状态码与响应体,且不依赖allow/deny顺序规则,语义更清晰:
- 简单拦截:在
server块顶部加if ($blocked) { return 403 "Access denied."; } - 静默丢弃:用
return 444;(Nginx特有,关闭连接不发响应) - 带日志拦截:配合
access_log off;或独立日志文件,避免黑名单请求刷爆日志
动态更新黑名单而不重载Nginx
硬编码在配置里不利于运维。可行方案包括:
- 用
include /path/to/blacklist.conf;,配合外部脚本生成该文件,再执行nginx -t && nginx -s reload - 使用OpenResty + Lua,通过共享字典(shared dict)实时增删IP,无需reload(适合高频变更场景)
- 借助Consul或Redis做外部IP列表,用Lua-resty-http定期拉取同步(需OpenResty环境)

















