用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,适合做准入控制。
Nginx
在宝塔面板中轻松管理Nginx高性能Web服务器。提供可视化配置反向代理、负载均衡、SSL证书及HTTP缓存功能,一键优化高并发性能,助您高效搭建稳定、快速的网站运行环境。
下载
最小可行实现(带注释)
在 server 块中加入:
}
生产环境必须注意的细节
连接池管理
:务必使用
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 实现)。
# 初始化 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
}
在每次请求的 Access 阶段检查
access_by_lua_block {
local redis = require "resty.redis"
local red = redis:new()
red:set_timeouts(500, 500, 500)
-- 复用连接池(更优做法,此处为简化)
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