nginx软封禁指基于后端返回状态码(如401/403/500)动态拦截请求,非ip黑名单式硬封禁;核心方式有三:proxy_next_upstream轻量拦截、map+limit_req状态码驱动限流、openresty+redis带衰减闭环封禁。

为什么用状态码做封禁依据?
传统封禁靠日志分析或固定规则(如高频访问),滞后且粗糙。而真实攻击往往表现为:
– 登录接口持续返回 401/403(暴力撞库)
– 支付回调反复触发 500/502(恶意重放或构造非法参数)
– 健康检查路径返回 503(探测后端存活状态)
这些是后端服务明确发出的“异常信号”,比单纯看请求频次更精准,也更贴近业务语义。
用 Nginx 实现软封禁的三种主流方式
1. 基于 proxy_next_upstream + 自定义错误页(轻量级,适合简单兜底)
当后端返回特定状态码时,Nginx 主动跳过当前节点,尝试下一个 upstream,并可配合 error_page 触发拦截逻辑:
- 在 upstream 块中启用容错:
proxy_next_upstream error timeout http_401 http_403 http_500 http_502; - 配置 error_page 将异常响应转为 444(直接关闭连接)或 403:
error_page 401 403 500 502 =444; - 注意:该方式只影响当前请求,不记录或累积行为,属于“单次软拦截”。
2. 利用 map + limit_req 实现“状态码驱动”的临时限流(推荐,平衡精度与性能)
思路是:把后端返回的异常状态码映射为一个可计量的 key,再用 limit_req 控制其单位时间内的触发次数:
- 在 http 块定义映射:
map $upstream_http_x_status_hint $status_key { default ""; "401" $binary_remote_addr; "403" $binary_remote_addr; "500" $binary_remote_addr; }(需后端在响应头中写入X-Status-Hint: 401) - 创建限流区:
limit_req_zone $status_key zone=badstatus:10m rate=3r/m; - 在 location 中启用:
limit_req zone=badstatus burst=3 nodelay; - 效果:某 IP 在一分钟内因后端返回 401 被记录 3 次后,后续请求直接被限流(503),持续 60 秒自动恢复。
3. OpenResty + Redis 实现带衰减的软封禁(生产级,支持策略联动)
这是真正意义上的“软封禁”闭环:检测异常响应 → 记录 IP 及原因 → 设置带 TTL 的封禁标记 → 下次请求内存级快速拦截。
- 后端在返回 401/500 时,通过 header 或 body 明确标识风险类型,例如:
X-Risk: brute_force - access_by_lua_file 脚本读取该 header,拼接 Redis key:
"softban:" .. ngx.var.remote_addr .. ":" .. ngx.var.upstream_http_x_risk - 调用
red:setex(key, 300, "1")设置 5 分钟封禁(TTL 可按风险等级分级) - 在 access 阶段前置检查:
if red:get("softban:" .. ip .. ":*") ~= nil then ngx.exit(403) end(支持通配匹配) - 优势:可区分暴力破解、参数篡改、爬虫探测等不同行为;封禁时间可动态调整;与风控系统 API 对接方便。
关键注意事项
– 不要直接依赖 $status:它是 Nginx 自身响应码(如 502),不是后端真实返回码。务必用 $upstream_http_* 系列变量获取后端响应头。
– 避免过度依赖 5xx:502/503 多由网络或负载问题引发,未必代表恶意,建议结合业务标识(如自定义 header)判断。
– 封禁粒度要合理:单 IP 封禁最安全;按 CIDR 封禁需谨慎,可能误伤;按 User-Agent 封禁易伪造,仅作辅助。
– 必须配监控和告警:比如统计每分钟被 softban 的 IP 数、Top 风险类型,防止策略被绕过或误触发。











