如何在Nginx配置文件中动态设置SSL证书密钥路径?
动态匹配Let's Encrypt证书实现多域名SSL服务
问题描述
我在/etc/letsencrypt/live/目录下有多个域名文件夹,每个文件夹包含对应的证书文件(fullchain.pem)和密钥文件(privkey.pem)。希望Nginx能为该目录下的任意域名提供SSL服务,并且后续会通过程序自动添加新域名。
当前Nginx配置如下:
server { listen 443 ssl; server_name default_server; map "$host" $domain_name { ~(.*)\.(.*)\.(.*)$ $2.$3; } ssl_certificate /etc/letsencrypt/live/$domain_name/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/$domain_name/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; client_max_body_size 20M; ... }
请求域名时出现错误:
No such file or directory:fopen('/etc/letsencrypt/live/default_server/fullchain.pem','r') error:2006D080:BIO routines:BIO_new_file:no such file) while SSL handshaking
可见$domain_name变量未正确解析,被替换成了default_server,请问该如何实现需求?
解决方案
出现这个问题的核心原因是Nginx在SSL握手阶段无法使用$host变量——SSL握手发生在HTTP请求之前,此时$host还未被解析,Nginx会直接使用server_name的值作为 fallback,也就是default_server,导致证书路径错误。
要实现动态匹配证书的需求,可以用以下两种方法:
方法1:使用ssl_certificate_by_lua(OpenResty推荐)
如果使用OpenResty(带Lua模块的Nginx),可以通过Lua脚本在SSL握手阶段动态获取域名并加载对应证书:
- 确保OpenResty已安装,在
http块中添加Lua配置:
http { lua_shared_dict cert_cache 10m; ssl_certificate_by_lua_block { local domain = ngx.ssl.server_name() local cert_path = "/etc/letsencrypt/live/" .. domain .. "/fullchain.pem" local key_path = "/etc/letsencrypt/live/" .. domain .. "/privkey.pem" local cert, err = io.open(cert_path, "r") if not cert then ngx.log(ngx.ERR, "Failed to open cert file: ", err) return ngx.exit(ngx.ERROR) end local cert_data = cert:read("*a") cert:close() local key, err = io.open(key_path, "r") if not key then ngx.log(ngx.ERR, "Failed to open key file: ", err) return ngx.exit(ngx.ERROR) end local key_data = key:read("*a") key:close() local ok, err = ngx.ssl.set_cert(cert_data) if not ok then ngx.log(ngx.ERR, "Failed to set cert: ", err) return ngx.exit(ngx.ERROR) end local ok, err = ngx.ssl.set_priv_key(key_data) if not ok then ngx.log(ngx.ERR, "Failed to set private key: ", err) return ngx.exit(ngx.ERROR) end } }
- 修改server块,移除原来的
ssl_certificate和ssl_certificate_key配置:
server { listen 443 ssl; server_name _; # 使用下划线匹配所有域名 include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; client_max_body_size 20M; ... }
这种方法能实时匹配新添加的域名,无需重启Nginx。
方法2:使用自动生成配置脚本(纯Nginx)
如果不想用OpenResty,可以编写脚本定期扫描证书目录,自动生成对应Nginx配置:
- 编写bash脚本,遍历证书目录生成配置片段:
#!/bin/bash NGINX_CONF_DIR="/etc/nginx/conf.d" CERT_BASE="/etc/letsencrypt/live" # 清空旧配置 rm -f ${NGINX_CONF_DIR}/dynamic_ssl.conf # 遍历所有域名文件夹 for domain in $(ls ${CERT_BASE}); do if [ -f "${CERT_BASE}/${domain}/fullchain.pem" ] && [ -f "${CERT_BASE}/${domain}/privkey.pem" ]; then cat >> ${NGINX_CONF_DIR}/dynamic_ssl.conf <<EOF server { listen 443 ssl; server_name ${domain}; ssl_certificate ${CERT_BASE}/${domain}/fullchain.pem; ssl_certificate_key ${CERT_BASE}/${domain}/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; client_max_body_size 20M; # 其他通用配置 ... } EOF fi done # 重载Nginx nginx -s reload
- 将脚本加入crontab,定期执行,即可自动适配新添加的域名。
注意事项
- 方法1需要OpenResty支持,适合需要动态实时生效的场景
- 方法2是纯Nginx方案,但需要定期重载配置,适合域名添加频率不高的场景
- 确保
/etc/letsencrypt/live/目录下的域名文件夹名称与实际请求的域名完全一致(比如请求test.example.com,文件夹名称必须是test.example.com),否则证书路径会出错
内容的提问来源于stack exchange,提问作者Najmus Sakib
相关产品推荐
相关产品推荐

