Nginx 换证书后启动失败,日志里出现 SSL_CTX_use_PrivateKey_file、key values mismatch 或 PEM_read_bio_PrivateKey,很多人第一反应是"证书文件坏了"。其实这几类报错可能分别对应证书与私钥不配对、路径权限不足、PEM 格式不完整或私钥仍加密。本文不改生产文件,按"材料配对---文件可读---配置加载---公网终点"四层排查,避免一上来就 reload,最后把锅甩给 Nginx。
一、先区分是 Nginx 没读到,还是读到了但不接受
ssl_certificate 指向服务端证书文件,ssl_certificate_key 指向对应私钥。Nginx 官方模块文档要求证书使用 PEM 格式;如果证书包含中间证书,服务器证书应放在前面,中间证书按链路顺序放在后面。启动阶段真正需要的是一对可解析、能配对的材料,不是文件名看起来像一对。
先看错误日志的第一处根因:key values mismatch 偏向公钥不匹配;Permission denied 偏向路径或权限;no start line、bad end line 偏向 PEM 边界或文件内容;如果日志提示需要密码,则还要确认自动启动场景能否安全解锁私钥。不同错误不要用同一个"重新申请证书"解决。
二、第一层:用公钥指纹确认证书与私钥配对
不要比较证书文件和私钥文件的文本,也不要用文件名判断配对关系。更稳妥的办法是分别导出公钥,再转成 DER 后计算摘要。RSA 和 ECDSA 都可以用同一条思路;两行摘要完全一致,才说明公钥材料相同。
openssl x509 -in server.crt -pubkey -noout \
| openssl pkey -pubin -outform DER \
| sha256sum
openssl pkey -in server.key -pubout \
| openssl pkey -pubin -outform DER \
| sha256sum
这是离线检查,不会暴露私钥内容。若私钥有密码,使用受控的 -passin 方式,不要把明文密码写进文章、脚本、Shell history 或 CI 日志。摘要不一致时,先回到证书签发记录和部署清单核对版本,不要强行让 Nginx 接受一对不匹配的材料。
三、第二层:确认读到的证书就是你想部署的那张
配对正确仍不代表部署正确。证书可能过期、SAN 不含目标域名、issuer 不符合预期,或者你改的是一份文件,Nginx 配置却指向另一份。先读出 subject、issuer、serial、有效期和 SAN,再与本次发布记录逐项对照。
openssl x509 -in server.crt -noout \
-subject -issuer -serial -dates
openssl x509 -in server.crt -text -noout \
| sed -n '/Subject:/,/X509v3 extensions:/p'
这里的 subject 只是身份字段,现代客户端还会检查 SAN;issuer 说明签发关系,不能单独证明客户端信任;serial 和 dates 用来确认版本与时间窗口。文件能被 openssl x509 解析,也不等于 Nginx 已加载它,更不等于公网终点已经更新。
四、第三层:沿路径检查权限和 PEM 边界
权限排查要从文件一路看到每一级目录。目录缺少执行权限,即使文件本身是可读的,服务进程也可能无法穿过目录。先识别 Nginx master 的实际启动用户和部署方式,再按最小权限验证;不要为了排障直接执行 chmod 777,也不要把私钥复制到公共目录。
namei -l /etc/nginx/ssl/example/fullchain.pem
namei -l /etc/nginx/ssl/example/server.key
stat -c '%A %U:%G %n' \
/etc/nginx/ssl/example/fullchain.pem \
/etc/nginx/ssl/example/server.key
证书和私钥通常不需要相同的权限,但私钥应限制读取主体。若私钥是加密 PEM,自动启动还会遇到解锁问题;能手工启动不代表 systemd、容器或面板重启时也能得到密码。此时应按部署架构选择受控的密码文件、密钥代理或人工解锁流程,并记录边界,不在日志里打印密钥。
五、第四层:先做配置检查,再让新 worker 接管
换文件后不要直接把 reload 当成成功证据。Nginx 官方控制文档说明,重新读取配置时会先检查语法并尝试打开日志和监听 socket;成功后启动新 worker,再让旧 worker 优雅退出,失败则保留旧配置继续工作。这个行为正好给了我们一个安全的验收顺序。
nginx -t && nginx -s reload
openssl s_client -connect example.test:443 \
-servername example.test -showcerts </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
命令只展示流程,路径和域名应替换为自己的隔离环境。nginx -t 通过,说明当前配置和材料至少能被 Nginx 检查;reload 返回成功,还要继续看 master/worker 日志和进程时间。最后用带正确 SNI 的客户端读取服务端实际返回的 subject、issuer 和 dates,不能只看本机文件的修改时间。
六、遇到"证书已换但客户端仍旧"时,检查终点而不是继续拷贝文件
如果源站已经返回新证书,而浏览器或外部探测仍看到旧证书,问题可能在 CDN、WAF、负载均衡、反向代理或多节点中的另一个终点。每个 TLS 终点都要用相同的 SNI 分别探测,并记录指纹、有效期和返回地址。只检查源站,会把边缘节点的旧版本误判成 Nginx 没更新。
还要区分 TLS 会话复用、DNS 解析差异和证书缓存现象。换一台网络、换一个解析结果或固定目标 IP 做对照,不能靠"浏览器刷新一下"当作证据。真正的收口标准是:访问者拿到预期证书,且每个受控终点都记录了版本。
七、故障对照与回滚边界
| 现象 | 优先检查 | 不要直接做 |
|---|---|---|
| key values mismatch | 证书与私钥公钥摘要 | 继续 reload |
| Permission denied | 目录链、文件属主与 master 用户 | chmod 777 |
| no start line | PEM 边界、换行和文件内容 | 把 DER 文件改后缀 |
| 源站新、边缘旧 | SNI、节点、DNS和代理层 | 只重启源站 |
自动部署还要保留上一版本的证书、私钥引用和配置检查结果。新版本通过配对、解析和 nginx -t 后再切换;失败时回到上一版本并重新检查,而不是删除旧文件"从头再来"。回滚记录至少包含版本、时间、目标节点和失败原因。
八、参考与最终验收
- 官方配置说明:Nginx SSL module;
- 官方控制说明:Nginx control;
- 证书签发路线:Let's Encrypt;
- 开源 ACME 客户端:acme.sh;
- 平台化方式:CertbotX。
最终清单很简单:证书和私钥公钥摘要一致;证书 SAN、issuer、有效期和版本符合预期;每一级目录可按最小权限访问;nginx -t 通过且日志无新错误;reload 后新 worker 已接管;每个公网 TLS 终点用正确 SNI 返回了新证书;旧版本和回滚路径仍可追溯。做到这里,证书才算真正换完。