用nginx+lua+redis实现动态ip封禁,核心是运行时决策、毫秒生效、无需reload;传统deny/geo依赖配置重载,无法条件封禁;lua查redis实现实时黑白名单与限速。

用 Nginx + Lua + Redis 实现动态 IP 封禁,核心在于把访问控制逻辑从静态配置移到运行时决策,让封禁动作毫秒级生效、无需 reload 配置。
为什么不用 nginx_http_geo_module 或 deny 指令?
传统 deny 和 geo 指令依赖配置文件重载,每次增删 IP 都要执行 nginx -s reload,不仅有短暂请求丢失风险,还无法支持按条件(如“5 分钟内请求超 100 次”)自动封禁。而 Lua 脚本可实时查 Redis、执行逻辑判断,真正实现“请求进来 → 查黑名单 → 决策放行/拦截”闭环。
关键组件职责划分
Redis:存储 IP 黑名单(建议用 SET 类型)和临时限速状态(用 INCR + EXPIRE 组合);
OpenResty(含 lua-resty-redis):提供高性能非阻塞 Redis 访问能力,避免 Lua 脚本阻塞 worker;
Nginx 的 access_by_lua* 指令:在 Access 阶段介入,早于 proxy_pass,适合做准入控制。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
最小可行实现(带注释)
在 server 块中加入:
# 初始化 Redis 连接池(推荐放在 init_by_lua*)
init_by_lua_block {
local redis = require "resty.redis"
local red = redis:new()
red:set_timeouts(1000, 1000, 1000)
-- 全局复用连接池,避免频繁建连
ngx.shared.redis_pool = red
}
<h1>在每次请求的 Access 阶段检查</h1><p>access_by_lua_block {
local redis = require "resty.redis"
local red = redis:new()
red:set_timeouts(500, 500, 500)</p><pre class="brush:php;toolbar:false;">-- 复用连接池(更优做法,此处为简化)
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "failed to connect to redis: ", err)
return
end
local ip = ngx.var.remote_addr
-- 查询 IP 是否在黑名单 SET 中
local found, err = red:sismember("ip_blacklist", ip)
if found == 1 then
ngx.status = 403
ngx.say("Access denied")
ngx.exit(403)
end
-- 可选:同时做简单频率限制(每分钟最多 30 次)
local key = "rate_limit:" .. ip
local current, err = red:incr(key)
if tonumber(current) == 1 then
red:expire(key, 60)
end
if tonumber(current) > 30 then
ngx.status = 429
ngx.say("Too many requests")
ngx.exit(429)
end}
生产环境必须注意的细节
- 连接池管理:务必使用 set_keepalive 复用连接,避免短连接打爆 Redis;
- 超时设置:Redis 调用必须设 timeout(毫秒级),防止网络抖动拖垮整个 worker;
- 降级策略:Redis 不可用时,建议默认放行(fail-open),或 fallback 到本地缓存(如 ngx.shared.DICT);
- IP 获取可靠性:若前端有 CDN 或 LB,需用 X-Forwarded-For 并校验可信跳数,避免伪造;
- 黑名单更新方式:通过外部脚本(Python/Shell)向 Redis 写入,或提供 Admin API(用 Lua + resty.http 实现)。










