给 Nginx 配置 HTTPS 不只是“申请一次证书,再写两个路径”。真正需要闭环的是四件事:域名控制权如何验证、证书如何安全部署、Nginx 如何加载新证书,以及续期失败时如何被发现。
选择证书范围
↓
选择 HTTP-01 或 DNS-01
↓
acme.sh 签发证书
↓
安装到 Nginx 专用路径
↓
nginx -t → reload
↓
验证线上证书与续期链路本文使用 example.com 和 www.example.com 作为示例。命令中的 DNS provider plugin、运行账户、文件路径和服务管理方式必须按实际环境替换。
先决定是否真的需要通配符
只服务主域名和 www 时,可以申请包含两个 SAN 的普通证书:
example.com
www.example.com如果需要动态增加 api.example.com、blog.example.com 等一级子域名,可以申请:
example.com
*.example.com通配符 *.example.com 只匹配一个左侧标签:
| 域名 | 是否被 *.example.com 覆盖 |
|---|---|
www.example.com | 是 |
api.example.com | 是 |
a.b.example.com | 否 |
example.com | 否,必须单独加入证书 |
不要因为“以后可能用到”就默认申请通配符。证书范围越大,同一私钥失陷后的影响面也越大。
HTTP-01 和 DNS-01 怎么选
普通域名优先考虑 HTTP-01
HTTP-01 会在站点的以下路径提供 challenge 文件:
http://example.com/.well-known/acme-challenge/...它要求公网能通过 80 端口访问对应域名,不能签发通配符证书。多台 Web 服务器还必须确保 challenge 文件在所有可能接收验证请求的节点上都可访问。
通配符必须使用 DNS-01
DNS-01 通过 _acme-challenge.example.com 下的 TXT 记录验证 DNS 控制权,支持通配符,也不要求 Web 服务器暴露在公网。
若要自动续期,应使用 DNS provider 的 API plugin。手工添加 TXT 记录只适合人工签发,每次续期仍需更新记录,不能称为无人值守自动续期。
DNS API token 应当:
- 只允许修改 ACME 所需的 DNS zone;
- 不使用账户级全局密钥;
- 不写进文章、Shell 历史、Git、日志或进程参数;
- 由 acme.sh 运行账户通过受限配置或环境读取;
- 条件允许时,在独立验证主机完成 DNS-01,再把证书部署到 Web 服务器。
安装 acme.sh 前先确定运行账户
acme.sh 的 home、账户配置、证书状态和定时任务都与安装账户相关。不要今天用普通用户签发,明天又用 root 的另一套 home 续期。
官方安装器会完成三件事:
- 把客户端安装到该账户的
~/.acme.sh/; - 创建 shell alias;
- 添加每日续期检查任务。
安装完成后先确认实际位置和版本:
~/.acme.sh/acme.sh --version
~/.acme.sh/acme.sh --list在生产服务器上直接执行远程 curl | sh 前,应先核对下载来源和安装脚本。也可以从官方 Git 仓库克隆后执行安装,避免在没有检查内容时把网络响应直接交给 Shell。
签发通配符证书
先从 acme.sh 的 DNS API 列表找到实际 provider plugin,并按该 provider 文档配置最小权限凭据。下例中的 dns_your_provider 不能直接使用:
~/.acme.sh/acme.sh --issue \
--server letsencrypt \
--dns dns_your_provider \
-d example.com \
-d '*.example.com'这里显式写出 --server letsencrypt,避免把“使用 acme.sh”和“必然使用 Let's Encrypt”混为一谈。acme.sh 支持多个 ACME CA,实际签发者取决于配置和命令参数。
签发失败时,优先检查:
- provider plugin 名称和凭据范围;
_acme-challengeTXT 是否已传播到权威 DNS;- 是否有旧 TXT 记录长期残留;
- 域名的权威 DNS 是否确实由当前 provider 管理;
- 是否触发 CA 的签发频率限制。
不要用连续 --force 重试替代排障。
用 --install-cert 部署证书
Nginx 不应直接引用 ~/.acme.sh/ 中的内部证书文件。该目录由 acme.sh 自己管理,结构可能变化。应使用 --install-cert 把私钥和完整证书链复制到稳定的服务路径。
例如为站点准备:
/etc/nginx/tls/example.com/privkey.pem
/etc/nginx/tls/example.com/fullchain.pem私钥只允许必要的管理账户读取,不能使用 0644 之类的全局可读权限。首次部署前应按本机 Nginx master 的运行方式和 acme.sh 部署账户设置 owner、group 与 mode。
~/.acme.sh/acme.sh --install-cert -d example.com --ecc \
--key-file /etc/nginx/tls/example.com/privkey.pem \
--fullchain-file /etc/nginx/tls/example.com/fullchain.pem \
--reloadcmd "nginx -t && nginx -s reload"是否需要 --ecc 取决于签发时使用的 key type。acme.sh 当前默认签发 ECC 证书;查询、安装和手动续期时必须操作同一份证书配置。
reloadcmd 是续期闭环的一部分:证书文件更新后,Nginx 仍要成功 reload 才会在新连接上使用它。示例使用 Nginx 自身的控制命令;如果本机由 systemd、容器编排或其他 supervisor 管理,应换成已经人工验证过的等价命令。
配置 Nginx TLS 与 HTTP/2
先检查版本和编译参数:
nginx -v
nginx -V当前 Nginx 使用独立的 http2 on; 指令:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name example.com www.example.com;
ssl_certificate /etc/nginx/tls/example.com/fullchain.pem;
ssl_certificate_key /etc/nginx/tls/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
root /srv/www/example.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}旧版 Nginx 可能不支持 http2 on;,只能使用旧的 listen 443 ssl http2; 写法;后者在新版已经弃用。不要混用两套语法,也不要在没有检查版本时机械复制。
示例把 www 规范化到主域名,并避免根据任意请求 Host 拼接重定向目标。若两个域名都应保留,需分别设计 canonical URL 和站点行为。
try_files $uri $uri/ =404 适合普通静态站点。只有确定站点是客户端路由的 SPA 时,才改为:
try_files $uri $uri/ /index.html;这与 HTTPS 无关,不应默认附加到所有 Nginx TLS 配置中。
先测试,再平滑 reload
修改配置或部署新证书后,先执行:
nginx -t它不仅检查语法,还会尝试打开配置引用的证书和其他文件,因此能发现路径、权限和 PEM 解析问题。通过后再执行:
nginx -s reloadreload 会启动使用新配置的 worker,再优雅关闭旧 worker。它通常比 restart 更适合证书更新,但仍应检查退出状态和 error log,不能只看到命令被调用就认定成功。
分三层验证结果
1. 检查部署到磁盘的证书
openssl x509 \
-in /etc/nginx/tls/example.com/fullchain.pem \
-noout -subject -issuer -dates -ext subjectAltName确认 SAN、签发者和 notAfter 与预期一致。不要在日志或工单中输出私钥。
2. 检查线上 Nginx 实际提供的证书
openssl s_client \
-connect example.com:443 \
-servername example.com \
-showcerts </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName-servername 发送 SNI,避免在多站点 Nginx 上检查到默认虚拟主机的证书。磁盘证书正确但线上仍是旧证书,通常说明 reload 未成功、流量经过其他 TLS 终止层,或请求落到了其他节点。
3. 检查 HTTP 行为
curl -I http://example.com
curl -I https://example.com
curl -I https://www.example.comcurl 适合检查跳转和 HTTP 状态,但不能单独替代 SAN、有效期和线上证书链检查。
自动续期需要单独验收
acme.sh 的定时任务会定期检查证书,不代表每次检查都会签发新证书。不要把证书有效期写死成固定天数;读取实际 notAfter,并让客户端根据 CA 信息与自身策略安排续期。
至少检查:
crontab -l
~/.acme.sh/acme.sh --list如果使用 systemd timer、容器或其他调度器,则检查对应机制,而不是假定一定存在 crontab。
续期验收应覆盖完整链路:
DNS challenge 可以自动完成
↓
CA 成功签发
↓
目标 key/fullchain 文件更新
↓
nginx -t 成功
↓
reload 成功
↓
线上 SNI 握手提供新证书不要在生产 CA 上频繁执行 --renew --force。需要演练时优先使用 CA 测试环境或受控测试域名,并确认测试证书不会覆盖生产路径。
故障按层定位
| 现象 | 优先检查 |
|---|---|
| DNS-01 验证超时 | 权威 DNS、TXT 内容、传播时间、provider API 权限 |
| HTTP-01 验证失败 | DNS A/AAAA、80 端口、challenge 路径、负载均衡节点 |
nginx -t 失败 | 证书路径、私钥权限、PEM 格式、配置语法 |
| 本机监听但公网不通 | 云安全组、主机防火墙、NAT/负载均衡 |
| 磁盘已更新但线上证书旧 | reload 结果、其他 TLS 终止层、多节点部署 |
| 自动续期未运行 | 安装账户、cron/timer、DNS token、客户端日志 |
云安全组、主机防火墙和 Nginx listener 是三层不同边界。先观察当前平台实际使用哪套机制,再做最小范围变更;不要把 firewall-cmd、ufw 或云厂商命令当成所有服务器都适用的固定步骤。
小结
可靠的 HTTPS 配置不是一次签发成功,而是能够持续回答:证书覆盖哪些域名、challenge 能否自动完成、私钥由谁读取、Nginx 是否真的加载了新证书,以及到期前失败能否被发现。
普通域名可优先使用 HTTP-01;通配符必须使用 DNS-01,并为 DNS API 凭据设置最小权限。证书通过 --install-cert 部署到稳定路径,Nginx 先测试再 reload,最后同时检查磁盘证书、线上 SNI 证书和续期调度,才算形成完整闭环。