服务器和代理

key values mismatch:Nginx 证书和私钥不匹配

作者: · 最后核实

报错信息nginx: [emerg] SSL_CTX_use_PrivateKey("/etc/nginx/ssl/example.com.key") failed (SSL: error:05800074:x509 certificate routines::key values mismatch)

key values mismatch 的意思是:Nginx 加载的私钥和它加载的证书不是一对。 Nginx 用 ssl_certificate 文件里的第一张证书和私钥比对,所以原因要么是私钥文件不对,要么是证书链文件的顺序不对。

快速修复

修法是找到和证书配对的私钥,或者把站点证书调到证书文件的最前面。先分别取出证书里的公钥和由私钥推导出的公钥,比较哈希:

openssl x509 -in /etc/nginx/ssl/example.com.crt -noout -pubkey | openssl sha256
openssl pkey -in /etc/nginx/ssl/example.com.key -pubout | openssl sha256
  • 哈希不同: 找到属于这张证书的私钥,或者用新的 CSR 重新签发证书。
  • 哈希相同: 说明你检查的这对没问题,要看 Nginx 实际引用的是哪个文件。执行 nginx -T | grep -E "ssl_certificate(_key)?" 列出所有路径,包括其他 server 块和 include 进来的配置。

然后测试并重载:

sudo nginx -t && sudo systemctl reload nginx

原因

最常见的是前两种:私钥来自另一次 CSR 或续期,证书链文件里中间证书放在了前面。

1. 私钥属于另一张证书或另一次 CSR

常见场景:CSR 生成了两次,证书按第二次签发,却用了第一次的私钥;不同续期批次的文件混在一起;把别的域名的私钥复制了过来。

  • 确认: 上面两个哈希不同。用同样方法检查其他候选私钥;下单时用的 CSR 里也有公钥:openssl req -in example.com.csr -noout -pubkey | openssl sha256。证书和 CSR 也可以放进密钥匹配工具比对。
  • 修复: 换成匹配的私钥。一个都对不上,就用 CSR 生成工具 或 OpenSSL 重新生成私钥和 CSR,请 CA 重签。

2. 证书链文件里中间证书在前

Nginx 把 ssl_certificate 文件里的第一张证书当作站点证书,后面的才是证书链。如果先拼了 CA 证书包,Nginx 就会拿你的私钥去和中间证书的公钥比对。

  • 确认: openssl x509 -in fullchain.crt -noout -subject 显示的是 CA 的名字,而不是你的域名。
  • 修复: 按“站点证书在前”重新拼接:
cat example.com.crt ca-bundle.crt > fullchain.crt

也可以用证书链补全工具生成顺序正确的文件。

3. 路径写反或指向了旧副本

  • 确认: nginx -T 显示 ssl_certificate_key 指向旧的备份目录,或者指向了证书文件。
  • 修复: 两条指令都指向当前这一对文件;Let's Encrypt 用 live/ 目录下的软链接,它们始终指向匹配的一对。

4. 粘贴时混入了多余内容

在 Windows 记事本或网页面板里复制粘贴,可能把两段 PEM 粘到一起,或者少了一行。

  • 确认: openssl x509 -in 文件 -noout 或 openssl pkey -in 文件 -noout 解析失败。
  • 修复: 从 CA 或面板重新下载文件,必要时用 dos2unix 转换换行符。

各环境修法

不管用宝塔、Certbot 还是 Kubernetes,私钥和证书都要来自同一次签发,证书文件里站点证书排在最前。

宝塔面板: 在“网站 → SSL → 其他证书”里,“密钥(KEY)”一栏粘贴私钥,“证书(PEM格式)”一栏粘贴站点证书加中间证书,站点证书必须在最前面。从阿里云、腾讯云下载证书时选“Nginx”类型,压缩包里的 .key 和 .pem(或 .crt)是一对,不要和别的批次混用。

Let's Encrypt(Certbot):

ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

acme.sh: 不要让 Nginx 直接读 ~/.acme.sh/ 下的文件;用 acme.sh --install-cert -d example.com --key-file ... --fullchain-file ... --reloadcmd "systemctl reload nginx",每次续期都会把两个文件一起复制过去。

商业证书: CA 会分别发站点证书和 CA 证书包,拼接时站点证书在前。私钥是你生成 CSR 时一起生成的那个,通常在服务器上或者生成 CSR 的平台里。

ECDSA + RSA 双证书: 配置两组 ssl_certificate / ssl_certificate_key 时,每个私钥要紧跟它自己的证书,顺序一一对应。

Docker / Kubernetes: TLS Secret 里的 tls.crt 和 tls.key 也必须匹配,用 kubectl get secret ... -o jsonpath 取出来后用同样的命令检查。

不要这样做

这 3 种做法修不好问题,还可能让损失扩大。

  • 给现有证书“重新生成一个私钥”。 不可能匹配,反而可能覆盖掉某处还保存着的正确私钥。
  • 不执行 nginx -t 就重启。 启动失败会让服务器上所有网站一起下线,不只是这一个。
  • 把私钥通过微信、邮件发给帮你排查的人。 发公钥哈希就够了;私钥一旦外泄,应该用新私钥重新签发证书。

常见问题

私钥丢了,能重新生成一个和证书匹配的吗?

不能。证书里写死了一个特定的公钥,只有当初一起生成的那个私钥才匹配。私钥丢了就重新生成私钥和 CSR,让 CA 重新签发;大多数 CA 在有效期内免费重签,阿里云、腾讯云控制台里也有“重新签发”入口。

能把私钥贴到在线工具里比对吗?

尽量不要。用“快速修复”里的命令在服务器本地比对公钥哈希即可;密钥匹配工具在浏览器本地运行、不上传任何内容,也可以用;但私钥最好始终不离开服务器。

为什么一续期就出这个错?

大多是只换了新证书、私钥还是旧的(或者反过来)。会轮换私钥的 ACME 客户端每次同时写两个文件,复制时要一起复制,或者直接让 Nginx 指向客户端的 live 路径(/etc/letsencrypt/live/<名称>/fullchain.pem 和 privkey.pem)。

这个错误会让整个 Nginx 挂掉吗?

restart 时会,reload 时不会。nginx -t 不通过,reload 被拒绝,原来正在运行的配置还能继续工作;但如果直接 restart,Nginx 会起不来,服务器上所有网站一起停。所以每次都先执行 nginx -t。

资料来源

  1. nginx ngx_http_ssl_module
  2. nginx: Configuring HTTPS servers
  3. OpenSSL openssl-x509
  4. OpenSSL openssl-pkey