? Nginx 动态封禁 IP 实战白皮书:从 CC 攻击到分布式实时防护
在真实生产环境中,爬虫、CC 攻击、暴力破解并不是偶发事件,而是长期存在的“背景噪音”。如果仍然依赖静态 deny 或重启 Nginx 生效的方式,不仅防护滞后,还极易误伤正常用户。
本文将从真实攻击场景出发,系统讲解如何在 Nginx / OpenResty 中实现 无需重启、实时生效、可分布式扩展 的动态 IP 封禁体系,并给出可直接落地的代码示例。
一、为什么静态封禁在生产中一定会失效?
❌ 传统方式的问题
deny 1.2.3.4;• 需要 reload 才生效 • 攻击响应慢 • IP 数量一多,配置文件失控 • 无法应对 IP 轮换、代理池
? 结论:
静态封禁只能作为“最后兜底”,不适合作为主防线。
二、动态封禁方案对比(选型依据)
| 生产首选 | |||||
本文核心方案:Lua + Redis
三、实战场景一:CC 攻击(高频但不打死)
? 攻击特征
• 单 IP / 小 IP 池 • 高频请求首页或 API • 不断线、不报错
? 防护目标
10 秒内超过 200 次请求 → 封禁 10 分钟
核心思路(滑动窗口)
1. Redis 记录 IP 请求计数 2. 超过阈值 → 写入封禁 Key 3. 后续请求实时拦截
Lua 生产级代码示例
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(50)
local ok, err = red:connect("redis", 6379)
if not ok then
return
end
local ip = ngx.var.remote_addr
local req_key = "req:cc:" .. ip
local ban_key = "ban:cc:" .. ip
-- 已封禁直接拦截
local banned = red:get(ban_key)
if banned ~= ngx.null then
ngx.exit(ngx.HTTP_FORBIDDEN)
end
-- 计数
local count = red:incr(req_key)
if count == 1 then
red:expire(req_key, 10)
end
if count > 200 then
red:setex(ban_key, 600, 1)
ngx.log(ngx.WARN, "CC BAN IP: ", ip)
ngx.exit(ngx.HTTP_FORBIDDEN)
end
red:set_keepalive(10000, 100)✅ 实战效果
• CC 攻击流量下降 90%+ • 正常用户无感知 • Redis QPS 稳定
四、实战场景二:登录接口暴力破解
? 攻击特征
• /login//wp-login.php• 高频 POST • 返回 401 / 403
Fail2ban 规则(识别攻击)
failregex = ^<HOST> .* "POST /api/auth/login" 401[nginx-login]
enabled = true
filter = nginx-login
logpath = /var/log/nginx/access.log
maxretry = 10
findtime = 60
bantime = 3600Fail2ban + Redis 联动
Fail2ban action:
redis-cli SETEX ban:login:<IP> 3600 1Lua 实时拦截:
local ban = red:get("ban:login:" .. ip)
if ban ~= ngx.null then
ngx.exit(ngx.HTTP_FORBIDDEN)
end五、实战场景三:恶意爬虫 / 扫描器
? 特征
• 高频 404 • 访问 /admin/phpmyadmin• UA 异常
UA + URI 联合封禁
local ua = ngx.var.http_user_agent or ""
local uri = ngx.var.uri
local bad_ua = {
"sqlmap",
"nmap",
"python-requests",
"curl"
}
for _, v in ipairs(bad_ua) do
if ua:lower():find(v) then
ngx.exit(ngx.HTTP_FORBIDDEN)
end
end
if uri:find("/phpmyadmin") or uri:find("/admin") then
ngx.exit(ngx.HTTP_FORBIDDEN)
end⚠️ UA 不可单独作为封禁依据,必须与行为组合
六、K8s / Ingress 场景下的正确姿势
❌ 常见误区
• 在 Pod 内运行 Fail2ban • 实际封禁的是 Pod IP
✅ 正确架构
Client → CDN → Ingress-Nginx(Lua) → Service → Pod
↓
RedisIngress 注入 Lua:
nginx.ingress.kubernetes.io/server-snippet: |
access_by_lua_file /etc/nginx/lua/ip_block.lua;七、生产环境关键经验(血泪总结)
一定要做
• 正确获取真实 IP • Redis Key 设置 TTL • 封禁日志可追踪
real_ip_header X-Forwarded-For;
real_ip_recursive on;千万别做
• Lua 写复杂业务逻辑 • 每请求新建 Redis 连接 • 无限增长黑名单
八、推荐的终极防护组合
九、结语
限流解决“洪水”,封禁解决“坏人”,Lua + Redis 解决“实时与分布式”。
真正可持续的安全体系,从来不是单点方案,而是多层协同。


