Nginx海量子域名场景下应采用动态证书加载:通过map指令实现按需解析证书,仅预加载fallback证书;超千域名时用OpenResty的ssl_certificate_by_lua*运行时加载,配合路径白名单、LRU缓存及mtime热感知,并配置fallback兜底保障握手不中断。

在海量小域名(比如成百上千个二级或三级子域名)场景下,Nginx 每次 reload 都要全量解析所有证书文件,会导致配置加载变慢、内存占用高、甚至短暂服务中断。这不是靠“写得更精简”能解决的,核心在于避免让 Nginx 在启动或重载时读取和验证全部证书。
用 $ssl_server_name + map 实现零证书预加载
不把证书路径硬写进每个 server 块,而是统一用一个 server 块处理所有域名,并通过 map 动态映射证书文件名:
- 定义映射关系,支持通配符和正则,例如:
map $ssl_server_name $cert_file {<br> hostnames;<br> *.shop.example.com "shop";<br> ~^user\d+\.example\.com$ "user";<br> default "fallback";<br>} - 在 server 块中直接引用:
ssl_certificate /etc/nginx/certs/$cert_file.crt;<br>ssl_certificate_key /etc/nginx/certs/$cert_file.key;
- Nginx 启动时只校验 fallback 证书,其余证书在首次 TLS 握手时才按需读取,跳过启动阶段的批量解析
启用 ssl_certificate_by_lua*(OpenResty 方案)
当域名数量超过几千、且证书更新频繁时,map 的静态映射仍不够灵活。此时应切换到 OpenResty 的运行时加载机制:
- 要求 OpenResty ≥ 1.19.3.1,且编译时启用了
--with-http_ssl_module - 禁用 session 缓存:
ssl_session_cache off;,防止旧会话复用已失效证书 - 在 Lua 脚本中用
io.open()安全读取 PEM 文件,并用lrucache缓存解析结果(含证书链与私钥的 OpenSSL 对象),避免每次握手都解析 PEM - 关键防护:路径白名单校验,拒绝
../类遍历请求,只允许读取/etc/letsencrypt/live/下的合法域名目录
证书存储与更新联动优化
证书不是“放那儿就行”,结构设计直接影响加载效率:
- 每个域名使用标准命名:
fullchain.pem和privkey.pem,不要合并、不要加密(私钥不能有密码) - 目录层级扁平化,例如:
/etc/letsencrypt/live/example.com/fullchain.pem,避免嵌套过深增加路径拼接开销 - 对接 Certbot 自动续期时,在 Lua 中监听文件 mtime 变化,或用
lua_shared_dict记录各域名证书最后修改时间,实现毫秒级热感知 - 避免用
include大量证书配置片段——那是配置爆炸的根源,不是优化
兜底与降级必须显式配置
动态加载失败不能导致连接直接断开,必须保障 TLS 握手始终能完成:
- server 块中保留一个 fallback 证书(如自签名或泛域名证书),并设为
server_name _匹配所有未命中域名 - 确保该 fallback 证书本身有效、可被 Nginx 启动时验证通过,否则整个 HTTPS 监听会失败
- 在 Lua 脚本中捕获
io.open或ssl.load_certificate异常,记录日志但不中断流程,自动回落到 fallback











