无需重启OpenResty即可动态切换HTTPS证书,核心是利用ssl_certificate_by_lua*在TLS握手阶段按SNI域名实时加载PEM格式证书与私钥,并配合LRU缓存和安全路径校验实现静默更新。

不需要重启 OpenResty 就能切换 HTTPS 证书,是支撑大规模多租户、SaaS 或泛域名服务的关键能力。核心在于绕过传统 Nginx 的静态证书配置限制,改用 Lua 在 TLS 握手阶段动态加载证书——这正是 OpenResty 的强项。
为什么必须动态加载?
当你的业务涉及数百个客户子域名(如 client1.example.com、client2.example.com)、或需按租户隔离证书、或频繁上线测试环境时,传统方式会迅速崩溃:
- 每个域名写一段
ssl_certificate配置,Nginx 配置文件膨胀到数万行,reload 耗时长、易出错; - 证书更新后必须 reload,哪怕只是单个域名,也会导致全站连接中断或 TLS 握手抖动;
- Let’s Encrypt 的 90 天有效期 + 自动续期机制,要求证书变更必须“静默”完成,不能影响在线请求。
关键组件与前提条件
动态加载不是开箱即用的功能,需确认以下几点已就位:
-
OpenResty 版本 ≥ 1.19.3.1(推荐最新稳定版),且编译时启用了
--with-http_ssl_module和--with-openssl=1.1.1+; - 使用
ssl_certificate_by_lua*指令(注意:不是ssl_certificate_by_lua_block,后者仅限特定版本); - 证书和私钥必须为 PEM 格式,且内容可由 Lua 安全读取(建议通过
lua-resty-lrucache缓存解析结果,避免每次握手都 IO + 解析); - 禁用
ssl_session_cache共享内存缓存(或设为off),否则旧会话可能复用过期证书。
最小可行实现(PEM 场景)
在 server 块中保留一个占位证书(fallback),再用 Lua 动态覆盖:
server {
listen 443 ssl;
server_name _; # 通配匹配所有 SNI 域名
<pre class="brush:php;toolbar:false;"># 占位证书(必须存在,否则启动失败)
ssl_certificate /path/to/fallback.crt;
ssl_certificate_key /path/to/fallback.key;
ssl_certificate_by_lua_block {
local ssl = require "ngx.ssl"
local cache = require "resty.lrucache".new(200)
-- 从 SNI 获取请求域名
local name, err = ngx.ssl.server_name()
if not name then
ngx.log(ngx.WARN, "no SNI name, using fallback")
return
end
-- 从缓存或磁盘加载对应证书(示例逻辑)
local cert_pem = cache:get(name .. "_cert")
local key_pem = cache:get(name .. "_key")
if not cert_pem or not key_pem then
cert_pem = read_cert_from_storage(name) -- 你自己的读取函数
key_pem = read_key_from_storage(name)
if cert_pem and key_pem then
cache:set(name .. "_cert", cert_pem, 3600)
cache:set(name .. "_key", key_pem, 3600)
end
end
if cert_pem and key_pem then
ssl.clear_certs()
local cert, err = ssl.parse_pem_cert(cert_pem)
local pkey, err = ssl.parse_pem_priv_key(key_pem)
if cert and pkey then
ssl.set_cert(cert)
ssl.set_priv_key(pkey)
end
end
}}
配套自动化:让证书“自己找上门”
动态加载只是下半段,上半段要确保新证书能及时落盘并被 Lua 读到:
- 用 Certbot + webroot 插件申请/续期,将证书统一存入结构化目录(如
/etc/letsencrypt/live/{domain}/fullchain.pem); - 续期后触发一个轻量脚本,向 OpenResty 进程发送自定义信号(如 USR1),通知其清空证书缓存;
- 或更稳妥地,用
inotifywait监听证书目录变更,自动 reload Lua 模块(无需 reload Nginx); - 务必对证书路径做白名单校验,防止恶意 SNI 名称触发任意文件读取。


















