Nginx原生不支持运行时动态加载多域名SSL证书,但可通过OpenResty+Lua的ssl_certificate_by_lua指令实现;需满足版本要求、按域名组织证书文件、在server块中配置动态加载逻辑并禁用静态证书指令,配合权限控制与缓存优化可支撑大规模SaaS/CDN场景。

Nginx 本身不支持运行时动态加载不同域名的 SSL 证书(即“一个 IP + 多个 SNI 域名 + 各自独立证书”需在配置 reload 时静态声明),但可通过 *OpenResty + Lua + ssl_certificate_by_lua 指令** 实现真正的动态证书加载,适用于托管成百上千个客户子域名的 SaaS、建站平台或 CDN 边缘节点场景。
一、确认环境支持动态 SSL
必须使用 OpenResty(≥1.21.4.1)或 Nginx ≥1.19.0 + ngx_http_ssl_module + lua-nginx-module 编译启用。
验证是否可用:
nginx -V 2>&1 | grep -o 'ssl_certificate_by_lua'
若无输出,说明未启用该模块,需重编译或改用 OpenResty 发行版。
二、准备证书存储与检索逻辑
证书不能硬编码在配置里,应按域名组织并可快速查取。推荐结构:
/etc/nginx/ssl_certs/ ├── example.com/ │ ├── fullchain.pem # 证书链(含中间 CA) │ └── privkey.pem # 私钥(权限 600!) ├── api.b2b.net/ │ ├── fullchain.pem │ └── privkey.pem ...
Lua 脚本中通过 server_name 变量查找对应目录,读取文件内容(注意:不能返回路径,必须返回 PEM 字符串)。
示例 Lua 函数(存为 /etc/nginx/lua/load_cert.lua):
local function read_file(path)
local f = io.open(path, "r")
if not f then return nil end
local content = f:read("*all")
f:close()
return content
end
local host = ngx.var.server_name
local base_dir = "/etc/nginx/ssl_certs/"
local cert_path = base_dir .. host .. "/fullchain.pem"
local key_path = base_dir .. host .. "/privkey.pem"
local cert = read_file(cert_path)
local key = read_file(key_path)
if cert and key then
ngx.ssl.clear_certs()
ngx.ssl.set_der_cert(ngx.ssl.parse_pem_cert(cert))
ngx.ssl.set_der_priv_key(ngx.ssl.parse_pem_priv_key(key))
else
-- 可 fallback 到默认证书,或返回错误
ngx.log(ngx.WARN, "No cert found for ", host)
return ngx.exit(444) -- 关闭连接
end三、Nginx 配置启用动态证书
在 http{} 块外(通常在 events{} 后)加载 Lua 模块,并在 server{} 中启用钩子:
# 加载 Lua 模块(OpenResty 默认已加载)
lua_package_path "/etc/nginx/lua/?.lua;;";
server {
listen 443 ssl;
server_name _; # 通配,所有 SNI 域名都走这里
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:...;
# 关键:动态证书加载入口
ssl_certificate_by_lua_block {
dofile("/etc/nginx/lua/load_cert.lua")
}
# 其他常规配置(root、proxy_pass 等)保持不变
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
}
}⚠️ 注意:
-
ssl_certificate_by_lua_block(或_file)必须在 listen 443 ssl 的 server 块内; - 不要设置
ssl_certificate或ssl_certificate_key指令,否则会覆盖 Lua 动态行为; - 所有域名必须通过 SNI 协议发送(现代浏览器/客户端均支持);
- 私钥文件权限务必设为
600,且 Nginx worker 进程需有读取权限(如nginx用户属组)。
四、安全与运维要点
- 证书热更新无需 reload:新增域名只需放好证书文件,首次请求即生效;
-
避免 Lua 报错中断 TLS 握手:建议在
load_cert.lua中加pcall包裹关键逻辑; -
性能考虑:Lua 中读文件是阻塞操作,高并发下建议用
lua-resty-lrucache缓存已加载的证书 DER 对象; - 日志与监控:在 Lua 中记录缺失证书的域名,便于自动告警补发;
-
fallback 机制:可预置一张泛域名或默认证书(如
_default.crt/.key),当客户证书缺失时降级使用,避免握手失败。
这种方案绕开了传统 Nginx “配置即代码”的限制,把证书管理交由外部系统(如数据库、Consul、对象存储)驱动,真正实现证书即服务(CaaS)。对中小规模多租户场景,比维护数千个 server{} 块更可持续。


















