Nginx中Lua脚本实现动态黑名单自动封禁机制

 更新时间:2026年04月27日 08:59:36   作者:舞夢輝影  
本文主要介绍了Nginx中Lua脚本实现动态黑名单自动封禁机制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

为什么直接用 access_by_lua_block 写封禁逻辑容易失效

因为 Nginx 的 Lua 模块(OpenResty)中,access_by_lua_block 阶段执行时,请求尚未进入 upstream 或日志阶段,但若后续有 rewrite_by_lua_block 或其他模块重写 URI、跳转,可能绕过该阶段;更关键的是,它不自动阻断后续处理——你得显式调用 ngx.exit(403),否则脚本跑完就继续往下走。

常见错误现象:ngx.var.remote_addr 被封了,但请求仍能访问后端;或日志里看到 IP 出现在黑名单里,HTTP 状态码却是 200。

  • 务必在封禁分支末尾加 ngx.exit(403),不能只设变量或写 Redis
  • 如果用了 try_files 或 error_page 重定向,要确认它们不会覆盖或跳过 access_by_lua_block
  • 避免在 access_by_lua_block 中做耗时操作(如 HTTP 请求),会阻塞 worker 进程

如何用 Redis + Lua 做毫秒级黑名单匹配

本地内存(如 shared_dict)适合短时频控,但跨 worker、跨实例的黑名单必须依赖外部存储。Redis 是最常用选择,关键是用原子操作避免并发查改导致漏放行。

推荐用 EVAL 执行一段内联 Lua 脚本,一次性完成「查是否存在」+「若存在则返回 1」,不依赖两次网络往返:

local exists = redis.call("EXISTS", "blacklist:" .. KEYS[1])
if exists == 1 then
  return 1
else
  return 0
end

在 Nginx 配置中调用:

access_by_lua_block {
  local red = require "resty.redis"
  local r = red:new()
  r:set_timeout(100)
  local ok, err = r: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
  local res, err = r:eval(
    "return redis.call('EXISTS', 'blacklist:' .. ARGV[1])",
    0, ip
  )
  if res == 1 then
    ngx.exit(403)
  end
}
  • 不要用 GET + IF 两步,竞态下可能刚查完就被删掉
  • Redis key 建议带前缀(如 blacklist:)并设置 TTL,避免长期堆积
  • 连接池比每次新建连接更高效,可用 resty.redis.connect + set_keepalive

怎么让封禁规则支持动态更新而不 reload Nginx

reload 会中断连接、丢失共享字典状态,不适合高频更新的黑名单。真正可行的方式是:把规则存 Redis,Nginx 每次请求都实时查——只要 Redis 响应够快(通常如果你真想降低 Redis 查询压力,可以用双层缓存:先查 shared_dict,未命中再查 Redis,并异步刷新本地缓存(用 lua-resty-lock 防穿透):

local dict = ngx.shared.blacklist_cache
local ip = ngx.var.remote_addr
local cached = dict:get(ip)
if cached == 1 then
  ngx.exit(403)
end
-- 加锁后查 Redis 并回填
local lock = require "resty.lock":new("locks")
local elapsed, err = lock:lock(ip)
if not elapsed then
  ngx.log(ngx.ERR, "failed to acquire lock: ", err)
  return
end
local red = --[[...redis init...]]
local res, _ = red:eval("return redis.call('EXISTS', 'blacklist:' .. ARGV[1])", 0, ip)
if res == 1 then
  dict:set(ip, 1, 60) -- 缓存 60 秒
  ngx.exit(403)
end
lock:unlock()
  • 别用定时拉取(如 timer.at)同步 Redis 到 shared_dict,worker 间不同步且易超时
  • shared_dict 的 TTL 必须比 Redis 的短,否则会出现“Redis 已解封,本地还拦着”的情况
  • 若业务对延迟极度敏感,考虑用 Redis 的 SCAN + 本地 Bloom Filter 预筛,但实现复杂度陡增

误封怎么快速放行又不影响线上

最安全的做法是提供独立管理接口(比如一个仅限内网访问的 /api/unban),由运维或自动化脚本调用,而不是手动改 Redis 或 reload。

示例接口只需一行命令:

location /api/unban {
  allow 10.0.0.0/8;
  deny all;
  content_by_lua_block {
    local ip = ngx.var.arg_ip
    if not ip or #ip == 0 then
      ngx.status = 400
      ngx.say("missing ip")
      return
    end
    local red = require "resty.redis":new()
    red:set_timeout(100)
    red:connect("127.0.0.1", 6379)
    red:del("blacklist:" .. ip)
    red:del("blacklist_cache:" .. ip) -- 清本地缓存
    ngx.say("ok")
  }
}
  • 严禁开放 DEL * 或无白名单的批量操作接口
  • Redis 的 DEL 是原子的,但 shared_dict 清除需各 worker 协同,所以建议用 key 名一致的命名空间(如都加 blacklist: 前缀),便于脚本批量清理
  • 记录所有封禁/解封操作到独立日志文件(用 ngx.log(ngx.INFO, ...)),排查误封时比翻 access.log 直观得多

实际部署时最容易被忽略的是 Redis 连接失败后的降级策略——默认行为是放行,但有些场景要求“宁可误杀也不能漏封”,这时就得在 redis:connect 失败时主动 ngx.exit(503),而不是静默跳过。这个决策点不在代码里,而在你的安全等级定义中。

到此这篇关于Nginx中Lua脚本实现动态黑名单自动封禁机制的文章就介绍到这了,更多相关Nginx Lua动态黑名单自动封禁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Nginx部署前端静态文件指南分享(基于虚拟机环境)

    Nginx部署前端静态文件指南分享(基于虚拟机环境)

    本笔记详细介绍了如何使用Nginx部署前端静态文件,包括环境准备、文件传输、配置文件编写、自定义配置加载、验证配置和日志查看等步骤,通过实践操作,用户可以掌握用Nginx快速部署任意前端静态文件的核心技能
    2026-02-02
  • Nginx部署多个vue项目的方法步骤

    Nginx部署多个vue项目的方法步骤

    本文主要介绍了Nginx部署多个vue项目的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • 记录一次nginx启动失败的解决过程

    记录一次nginx启动失败的解决过程

    小编最近遇到这样一个问题docker nginx起不来了,导致jira域名映射失败,如何解决呢?下面小编给大家分享下nginx启动失败的解决过程,感兴趣的朋友一起看看吧
    2022-02-02
  • Nginx重新编译添加模块的方法

    Nginx重新编译添加模块的方法

    这篇文章主要介绍了Nginx重新编译添加模块的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-07-07
  • Ubuntu使用nginx搭建webdav文件服务器的详细过程

    Ubuntu使用nginx搭建webdav文件服务器的详细过程

    今天通过本文给大家分享Ubuntu使用nginx搭建webdav文件服务器的详细过程,在这小编提示大家在安装nginx时需要先安装nginx-full,具体安装方法跟随小编一起通过本文学习下吧
    2021-05-05
  • Nginx的auth_request模块的应用小结

    Nginx的auth_request模块的应用小结

    Nginx的auth_request模块通过子请求实现动态鉴权,支持JWT验证、API鉴权等场景,本文就来详细的介绍auth_request模块的使用,具有一定的参考价值,感兴趣的可以了解一下
    2026-04-04
  • Nginx可视化管理软件(Nginx Proxy Manager)的使用

    Nginx可视化管理软件(Nginx Proxy Manager)的使用

    Nginx Proxy Manager是一款开源的Nginx可视化管理界面,本文就来介绍一下Nginx Proxy Manager的使用,感兴趣的可以了解一下
    2024-03-03
  • 在Nginx服务器下配置StartSSL和SSL的教程

    在Nginx服务器下配置StartSSL和SSL的教程

    这篇文章主要介绍了在Nginx服务器下配置StartSSL和SSL的教程,其中申请证书的步骤确实比较麻烦一些,不过出于安全考虑:p需要的朋友可以参考下
    2015-07-07
  • 一次nginx崩溃事件的实战记录

    一次nginx崩溃事件的实战记录

    nginx是一个被广泛使用的集群架构组件,我们有必要对它有足够的了解,下面这篇文章主要给大家介绍了一次nginx崩溃事件的相关资料,文中通过图文以及实例代码介绍的非常详细,需要的朋友可以参考下
    2023-01-01
  • Nginx配置禁止访问某个目录或文件的方法和示例

    Nginx配置禁止访问某个目录或文件的方法和示例

    在 Nginx 中,可以通过配置 访问控制规则 来禁止访问某个目录或文件,以下是详细的配置方法和示例,帮助你快速实现禁止访问的需求,需要的朋友可以参考下
    2025-11-11

最新评论