nginx原生不支持直接展开环境变量如$ssl_cert_path,必须通过envsubst模板替换或openresty的lua动态加载实现;推荐容器化部署中使用envsubst预生成配置,兼顾兼容性与安全性。

Nginx 原生不支持在配置中直接展开 $SSL_CERT_PATH 这类环境变量,所以不能写成 ssl_certificate $SSL_CERT_PATH; 就生效。真正可行的方式,是在容器启动前用模板替换生成最终配置,或借助 OpenResty 的 Lua 能力在运行时动态加载。
用 envsubst 模板替换(最常用)
这是容器化部署中最稳妥、最广泛采用的做法。核心是把 Nginx 配置写成模板文件,用 envsubst 把环境变量值注入进去,再启动 Nginx。
- 准备模板
/etc/nginx/conf.d/default.conf.template:
server {
listen 443 ssl;
server_name ${SERVER_NAME};
ssl_certificate ${SSL_CERT_PATH};
ssl_certificate_key ${SSL_KEY_PATH};
location / { proxy_pass http://backend; }
}
- 运行容器时执行两步:先替换模板,再启动 Nginx
- 确保镜像已安装
gettext(提供envsubst),例如 Alpine 镜像需apk add --no-cache gettext - 挂载证书目录时注意权限:宿主机证书文件应设为
644(证书)和600(私钥),容器内 Nginx 用户需有读取权限
在 Dockerfile 中预处理(适合固定路径场景)
如果证书路径在构建阶段就确定(如测试环境用自签名证书),可在镜像打包时完成替换,避免每次启动都执行脚本。
- Dockerfile 中添加:
FROM nginx:alpine
COPY default.conf.template /etc/nginx/conf.d/default.conf.template
ENV SSL_CERT_PATH=/etc/ssl/certs/app.crt
ENV SSL_KEY_PATH=/etc/ssl/private/app.key
RUN apk add --no-cache gettext && \
envsubst /etc/nginx/conf.d/default.conf
- 这样构建出的镜像启动后可直接运行
nginx -g "daemon off;",无需额外命令 - 缺点是证书路径无法在运行时变更,灵活性较低
用 OpenResty + Lua 动态加载(高并发多域名场景)
当需要支撑大量子域名、SaaS 多租户或 Let’s Encrypt 自动续期时,OpenResty 的 ssl_certificate_by_lua_file 是更优解——它在 TLS 握手阶段按 SNI 实时加载证书,不依赖 reload,也无停机风险。
- 要求 OpenResty ≥ 1.19.3.1,编译时启用
--with-http_ssl_module和 OpenSSL 1.1.1+ - 禁用会话缓存:
ssl_session_cache off;,防止复用旧证书会话 - Lua 脚本中通过
ngx.var.ssl_server_name获取域名,安全读取对应 PEM 文件(路径需白名单校验) - 必须配置 fallback 证书:用
server_name _匹配所有未命中域名,提供兜底 TLS 握手能力
注意事项与常见坑点
无论选哪种方式,都要注意以下几点:
- 证书和私钥文件必须是标准 PEM 格式,且私钥不能加密(否则 Nginx 或 Lua 无法直接读取)
- 挂载证书卷时,推荐只读挂载(
:ro),并确认容器内 Nginx 工作用户(如nginx或www-data)对路径有读权限 - 不要尝试在配置里写
ssl_certificate /etc/nginx/ssl/$SSL_CERT;—— Nginx 不解析变量,会导致启动失败 - 若用 Lua 方式,建议用
lua-resty-lrucache缓存证书内容,避免每次握手都触发磁盘 IO











