私有 CA 与排错

OpenSSL 常见报错排查:含义、诊断命令与修复

作者: · 最后核实 实测 macOS · OpenSSL 3.6.1

下面每条常见 OpenSSL 报错都附有定位问题的命令和修复办法。报错原文按 OpenSSL 3.6.1 的输出引用,1.1.1 或 3.0 写法不同时一并列出。如果出问题的是公网网站,可以先用 SSL 检测工具看一眼;想精确看到 OpenSSL 眼中的情况,再用下面的命令。

解码任意 OpenSSL 错误码

openssl errstr 把报错行里的十六进制错误码翻译成原因。

openssl errstr 0A000086 0A000410 0308010C
error:0A000086:SSL routines::certificate verify failed
error:0A000410:SSL routines::ssl/tls alert handshake failure
error:0308010C:digital envelope routines::unsupported

完整的一行长这样:80FF14F601000000:error:0A000086:SSL routines:tls_post_process_server_certificate:certificate verify failed:ssl/statem/statem_clnt.c:2126:。第一段是线程号,后面依次是错误码、库、函数和原因。遇到 certificate verify failed 时,真正的原因在它上面那行 verify error:num=。

解决 unable to get local issuer certificate(错误 20)

客户端找不到给服务器证书签名的那张证书:多数是服务器漏发了中间证书,少数是客户端缺根证书。

openssl s_client -connect www.example.com:443 -servername www.example.com -showcerts < /dev/null 2>&1 \
  | grep -E 'verify error| [0-9] s:|Verify return'
verify error:num=20:unable to get local issuer certificate
verify error:num=21:unable to verify the first certificate
 0 s:CN=www.example.com
Verify return code: 21 (unable to verify the first certificate)

证书链里只有一张证书,说明服务器缺中间证书:Certbot 要配 fullchain.pem,商业证书要把服务器证书和 CA 证书包拼在一起;宝塔面板里“证书(PEM 格式)”一栏要粘贴完整证书链。证书链完整却仍然报错,就是客户端信任库缺根证书:更新 ca-certificates 或用 -CAfile 指定。和 curl 错误 60、缺少中间证书是同一个根因。

解决 certificate has expired(错误 10)与 not yet valid(错误 9)

拿证书的有效期和执行检查的那台机器的时钟比一比。

openssl s_client -connect www.example.com:443 -servername www.example.com -showcerts < /dev/null 2>/dev/null \
  | grep -E ' [0-9] s:|v:NotBefore'
date -u
 0 s:CN=www.example.com
   v:NotBefore: Nov  3 13:44:34 2026 GMT; NotAfter: Feb  1 13:44:33 2027 GMT
 1 s:C=CN, O=Example Corp, CN=Example Intermediate CA
   v:NotBefore: Sep  3 00:00:00 2025 GMT; NotAfter: Sep  2 23:59:59 2028 GMT
Tue Nov 10 02:30:00 UTC 2026

服务器证书过期就续期并重载服务:磁盘上的文件换了,Nginx 或 Apache 不 reload 是不会生效的,宝塔面板续签后也要确认 Nginx 已重载。链上的中间证书过期,就换成 CA 当前提供的证书包。证书明明有效却报错 9 或过期,多半是本机时钟不对,常见于没有配 NTP 的嵌入式设备和内网机器。浏览器里对应的是 NET::ERR_CERT_DATE_INVALID。

解决 hostname mismatch(错误 62)

你访问的域名不在证书的 SAN(使用者备用名称)里。

openssl s_client -connect www.example.com:443 -servername shop.example.com \
  -verify_hostname shop.example.com < /dev/null 2>/dev/null | grep -E 'verify error|Verify return'
openssl x509 -in cert.pem -noout -ext subjectAltName
verify error:num=62:hostname mismatch
Verify return code: 62 (hostname mismatch)
X509v3 Subject Alternative Name: 
    DNS:www.example.com, DNS:example.com

要么重新签发把缺的域名加进 SAN,要么让客户端改用证书里有的域名。通配符只匹配一级子域名。如果返回的证书属于另一个网站,说明服务器因为没收到 SNI,或者这个域名没有对应的站点配置,返回了默认证书;宝塔面板里没绑定该域名的站点就会出现这种情况。浏览器里对应的是 NET::ERR_CERT_COMMON_NAME_INVALID。

解决 self-signed certificate in certificate chain(错误 19)

证书链的顶端是客户端不信任的根证书,通常是私有 CA,或者做 TLS 解密的代理、安全软件。

openssl s_client -connect www.example.com:443 -servername www.example.com < /dev/null 2>&1 \
  | grep -E '^depth|verify error' | head -4
depth=2 C=CN, O=Example Corp, CN=Example Root CA
verify error:num=19:self-signed certificate in certificate chain
depth=1 C=CN, O=Example Corp, CN=Example Intermediate CA
depth=0 CN=www.example.com

顶端如果是你们公司自己的 CA,就把它加进客户端信任库(见搭建私有 CA),不要关掉证书校验。如果是陌生的企业或杀毒软件 CA,说明流量被中间设备解密了。错误 18(self-signed certificate)表示服务器证书本身就是自签名的。OpenSSL 1.1.1 中这两个错误写作不带连字符的 self signed。更多见 self-signed certificate in certificate chain。

解决 wrong version number 与 packet length too long

这两个报错说明对方根本没用 TLS 回应,最常见的是连到了明文 HTTP 端口。

openssl s_client -connect www.example.com:80 < /dev/null
curl -sI http://www.example.com:443/ | head -1
...:error:0A0000C6:SSL routines:tls_get_more_records:packet length too long:...
...:error:0A000139:SSL routines::record layer failure:...
HTTP/1.1 200 OK

OpenSSL 1.1.1、3.0 一般报 wrong version number;较新的版本(这里实测 3.6)遇到同样情况可能报 packet length too long。curl 那一行说明 443 端口在用明文 HTTP 应答。检查 Nginx 的 listen 443 ssl 是否漏了 ssl、客户端配置的端口,以及代理是否要求先发 CONNECT。邮件客户端在 STARTTLS 端口上直接用 SSL(587 与 465 搞反)也会这样。

解决 handshake failure(告警 40)与 no shared cipher

客户端和服务器找不到共同支持的协议版本、密码套件、密钥交换组或签名算法。

openssl s_client -connect www.example.com:443 -servername www.example.com < /dev/null 2>&1 | grep -m1 alert
openssl s_client -connect www.example.com:443 -servername www.example.com -tls1_2 -cipher 'ALL:@SECLEVEL=0' -brief < /dev/null
...:error:0A000410:SSL routines:ssl3_read_bytes:ssl/tls alert handshake failure:...:SSL alert number 40

服务器日志里对应的是 no shared cipher。典型原因有:套件列表把客户端支持的全排除了;只部署了 ECDSA 证书而客户端只接受 RSA;ssl_ecdh_curve/groups 设置过窄;或者 TLS 1.2 服务器强制要求客户端证书。1.1.1 等老版本对同一告警显示 sslv3 alert handshake failure。浏览器里显示为 ERR_SSL_VERSION_OR_CIPHER_MISMATCH,对比双方列表的方法见密码套件与协议。

解决 no protocols available 与 alert protocol version(告警 70)

no protocols available 是你自己的客户端拒绝了,alert protocol version 是服务器拒绝了你提供的版本。

openssl s_client -connect www.example.com:443 -tls1_1 < /dev/null
openssl s_client -connect www.example.com:443 -tls1_1 -cipher 'DEFAULT:@SECLEVEL=0' < /dev/null
...:error:0A0000BF:SSL routines:tls_setup_handshake:no protocols available:...
...:error:0A00042E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version:...:SSL alert number 70

OpenSSL 3.x 在默认安全级别下禁用 TLS 1.0/1.1,所以基于它的客户端连不上只支持旧协议的老服务器,要等服务器升级到 TLS 1.2。内网里还有老设备必须继续用的话,应尽快升级设备,降低 @SECLEVEL 只能临时救急。浏览器里显示为 ERR_SSL_PROTOCOL_ERROR 或版本不匹配。

解决 key values mismatch(证书与私钥不匹配)

证书和私钥不是一对,或者证书链文件把中间证书放在了第一位。

openssl x509 -in fullchain.pem -noout -subject
openssl x509 -in fullchain.pem -noout -pubkey | openssl sha256
openssl pkey -in privkey.pem -pubout | openssl sha256
subject=C=CN, O=Example Corp, CN=Example Intermediate CA
SHA2-256(stdin)= d6f7b12a13ec99a751aad52deee4ce9339710d60fb07acefa86eb1e8d753648d
SHA2-256(stdin)= 3152719d149366da47e37904e2740fb6d296ade304092ce5308560b45d75e458

这里文件里的第一张是中间证书,Nginx 拿错了公钥去比:把服务器证书放到最前面(cat leaf.pem intermediate.pem > fullchain.pem)。如果第一张确实是自己的域名但哈希不同,说明私钥属于别的证书,常见于重新生成过 CSR、或者在云控制台下载证书时混用了旧私钥;找到对应私钥或重新签发。Nginx 里的报错就是 key values mismatch。

解决读取 PKCS#12 时的 unsupported

OpenSSL 3.x 把 RC2 等老算法移到了 legacy provider,读取老的 .pfx/.p12 要加 -legacy。

openssl pkcs12 -in old.pfx -info -noout
openssl pkcs12 -in old.pfx -info -noout -legacy
MAC: sha1, Iteration 2048
MAC length: 20, salt length: 16
PKCS7 Encrypted data: pbeWithSHA1And40BitRC2-CBC, Iteration 2048
Error outputting keys and certificates
...:error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:...:Algorithm (RC2-40-CBC : 0), Properties ()
...
Certificate bag
Certificate bag

加上 -legacy 的第二条就能正常读取。想把文件升级成新格式,先用 -legacy 解出来,再不带 -legacy 重新导出,得到的就是 OpenSSL 3.0 起默认的 AES-256-CBC + PBKDF2。只有老软件读不了新格式时,才反过来用 -export -legacy。早年从 Windows 导出、或由老版本工具生成的 PFX 常需要这样处理,详见 PKCS#12 命令。

解决 Mac verify error: invalid password?

PKCS#12 密码错了,或者文件密码是空的而你没给(反过来也一样)。

openssl pkcs12 -in bundle.p12 -noout -passin pass:wrong
openssl pkcs12 -in bundle.p12 -noout -passin pass:
Mac verify error: invalid password?

第二种写法显式传入空密码,Windows 不设密码导出的文件、用空密码转换的 Java 密钥库就是这种情况。云厂商下载的 IIS/Tomcat 格式证书包里一般附带一个密码文本文件,别和别的证书包搞混。在多人共用的机器上不要把真实密码写在命令行里,用 -passin file:pass.txt 或 env:变量名。

解决加载私钥时的 bad decrypt

加密私钥的口令不对。

openssl pkey -in privkey.pem -noout
head -2 privkey.pem
Could not find private key of key from privkey.pem
...:error:1C800064:Provider routines:ossl_cipher_unpadblock:bad decrypt:...
...:error:11800074:PKCS12 routines:PKCS12_pbe_crypt_ex:pkcs12 cipherfinal error:...:maybe wrong password
-----BEGIN ENCRYPTED PRIVATE KEY-----

看到 ENCRYPTED PRIVATE KEY(PKCS#8)或者 Proc-Type: 4,ENCRYPTED 头(传统格式),就说明私钥是加密的。Web 服务器启动时没法交互输入口令,确认口令无误后,导出一份不加密的副本并收紧权限:openssl pkey -in privkey.pem -out privkey-plain.pem && chmod 600 privkey-plain.pem。

解决 Could not find certificate 与 no start line

OpenSSL 没找到预期类型的 PEM 块:文件拿错了、其实是 DER,或者内容被破坏。

file cert.pem
head -1 cert.pem
openssl x509 -in cert.pem -noout -subject
openssl asn1parse -in cert.pem | head -3
cert.pem: ASCII text
-----BEGIN CERTIFICATE-----
Could not find certificate from cert.pem
...:error:1E08010C:DECODER routines:OSSL_DECODER_from_bio:unsupported:...:No supported data to decode. Input structure: Certificate
...:error:0680009B:asn1 encoding routines:ASN1_get_object:too long:...

OpenSSL 1.1.1 对应的报错是 unable to load certificate ... no start line。asn1parse 报 too long 或 header too long,说明 Base64 正文被截断或改动过,通常是复制粘贴时少了一行,重新获取文件即可。file 显示 data 或 Certificate 时是 DER 格式,加 -inform DER。OpenSSL 3.6 本身能容忍 UTF-8 BOM 和 Windows 换行(CRLF),但 Java、Go 和一些控制面板不行:在 Windows 记事本里编辑过的证书,用 dos2unix cert.pem 或 sed -i '1s/^\xEF\xBB\xBF//' cert.pem 处理一下。把私钥当证书传进去也是同样的报错,所以先看第一行。格式转换见证书格式转换。

解决 unknown ca(告警 48)与 certificate required(告警 116)

在双向 TLS 中,这两个告警来自服务器:它拒绝了客户端证书,或者根本没收到。

openssl s_client -connect api.example.com:443 -servername api.example.com -brief < /dev/null 2>&1 | grep alert
openssl s_client -connect api.example.com:443 -servername api.example.com \
  -cert client.crt -key client.key -brief < /dev/null 2>&1 | grep alert
...:error:0A00045C:SSL routines:ssl3_read_bytes:tlsv13 alert certificate required:...:SSL alert number 116
...:error:0A000418:SSL routines:ssl3_read_bytes:tlsv1 alert unknown ca:...:SSL alert number 48

去掉 -brief 运行 s_client,看 Acceptable client certificate CA names,就知道服务器接受哪些 CA;然后出示由其中之一签发的证书,并用 -cert_chain 带上中间证书。公网 TLS 证书越来越多地不再包含 clientAuth EKU,校验 EKU 的服务器会拒绝它们,见客户端认证 EKU 被移除。服务器日志里会有对应的 certificate verify failed 及具体原因。

常见问题

那一长串 OpenSSL 报错怎么看?

格式是“线程号:error:错误码:库:函数:原因:源文件:行号:附加信息”。真正有用的是原因(比如 certificate verify failed)和十六进制错误码;在日志里看到错误码,可以用 openssl errstr <错误码> 查原因。源文件和行号指向 OpenSSL 源码,一般帮不上忙。

为什么浏览器正常,curl、Python、Java 却报证书错误?

浏览器自带根证书库,还会替你补全缺失的中间证书;命令行工具和各语言运行时用的是系统信任库、自带证书包(certifi、Java cacerts),或者什么都没有。先把服务器证书链配完整,再确认运行时信任正确的根证书。参见 curl 错误 60 和 Java PKIX path building failed。

Node.js 报 error:0308010C:digital envelope routines::unsupported,是证书问题吗?

通常不是。这个错误码只表示 OpenSSL 3 拒绝了一个已移入 legacy provider 的老算法。在 Node.js 17 及以上,多半是老版本构建工具用 MD4 算哈希,升级工具即可;如果出现在 PKCS#12 文件上,才是 RC2 或 3DES 加密的问题,加 -legacy 解决。

资料来源

  1. OpenSSL 手册:openssl-verify(验证错误码)
  2. OpenSSL 手册:X509_STORE_CTX_get_error
  3. OpenSSL 手册:openssl-pkcs12
  4. OpenSSL 3.0 迁移指南
  5. RFC 8446 第 6 节:告警协议