支撑万级域名HTTPS需OpenResty动态证书加载:要求版本≥1.19.3.1、启用SSL模块、禁用会话缓存、用ssl_certificate_by_lua_file;证书扁平化存储、权限控制、LRU缓存DER格式;fallback server兜底;ACME续期通过mtime和shared_dict热感知。

要支撑万级域名的 HTTPS 服务,靠传统 server 块堆叠或定期 nginx -s reload 完全不可行——配置膨胀、热更新中断、证书轮换延迟都会成为瓶颈。真正可行的架构核心,是把证书加载从“配置阶段”移到“TLS 握手阶段”,由 OpenResty 在内存中按需解析与绑定。
必须满足的底层能力前提
动态证书加载不是开箱即用的功能,以下四点缺一不可:
- OpenResty 版本 ≥ 1.19.3.1(建议 1.21.4+),确保
ssl_certificate_by_lua*系列指令稳定可用 - 编译时启用
--with-http_ssl_module且 OpenSSL ≥ 1.1.1(支持 ALPN、SNI 和 PEM 解析) - Nginx 配置中显式关闭会话缓存:
ssl_session_cache off,否则旧会话可能复用已失效证书 - 禁用
ssl_certificate_by_lua_block(仅限调试),生产环境必须用ssl_certificate_by_lua_file保证初始化隔离与错误收敛
证书存储与安全读取设计
万级域名意味着文件系统 IO 和路径安全性是关键风险点。不能让 Lua 脚本自由拼接路径,也不能每次握手都打开磁盘文件。
- 证书目录结构按域名严格扁平化,例如:
/etc/nginx/certs/example.com/fullchain.pem,不嵌套子目录或通配符 - 私钥权限必须为
600,且 Lua 中只允许读取白名单前缀路径(如/etc/nginx/certs/),拒绝任何含..的路径遍历尝试 - 使用
lua-resty-lrucache缓存已解析的 DER 格式证书与密钥,键为域名 + mtime 时间戳,避免重复 PEM 解析开销 - 证书必须为无密码 PEM,否则
ngx.ssl.parse_pem_priv_key()会静默失败
兜底机制与 fallback server 块
动态加载失败不能导致 TLS 握手直接断连——这会引发大量客户端报错。必须设计有层次的 fallback:
- 定义一个全局
server块监听443 ssl,server_name _匹配所有未命中域名 - 该块配置一个低优先级但始终有效的 fallback 证书(如自签名根签发的泛域名证书),确保握手能完成
- 在 Lua 脚本中,若查不到对应域名证书,不调用
ngx.ssl.set_der_*,而是让 Nginx 自然回落到该块的静态证书 - 同时记录
ngx.log(ngx.WARN),并可对接 Prometheus 暴露ssl_cert_load_failures_total{domain="xxx"}指标
对接 ACME 自动续期的轻量热感知
Let’s Encrypt 证书每 60 天更新一次,但不需要重启或 reload。关键是让 Lua 层“感知变化”:
- 在 Lua 加载逻辑中,对每个域名缓存其证书文件的
mtime;每次握手时比对当前 mtime,不一致则重新读取并更新缓存 - 利用
lua_shared_dict存储各域名最后加载时间戳,跨 worker 进程共享状态,避免多进程重复加载 - Certbot 续期后触发一个轻量脚本,向
lua_shared_dict写入 “dirty” 标记,下一次握手自动刷新,无需外部信号 - 禁止在 Lua 中执行
os.execute("nginx -s reload")类操作——它破坏了动态加载的设计初衷,也带来竞争风险


















