服务器明明把根证书也发出来了,OpenSSL却报self-signed certificate in certificate chain,curl仍然不信任。这里容易把“链里有根”与“客户端信任根”混成一件事。本文用临时私有CA和回环HTTPS对照,定位error 19,并解释为什么删掉服务端根证书后,错误文字变了,问题却没修好。
一、error 19不是说叶证书一定自签
OpenSSL把这个错误标为X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:利用不受信任的证书材料可以构建到根,但本地没有找到可信的根。这里的“不受信任材料”是构链输入的角色,不是已经判定每张证书伪造。
叶证书完全可以由中间CA签发。链条走到了自签根,仍缺少客户端认可它的信任依据。对端递来的“身份证明册”里有盖章的人,不代表这个盖章人自动进入你的信任名单。
同时保留完整错误和depth。本文三层链中叶子为depth 0,中间CA为1,根为2;19是X.509验证错误号,不是Shell进程退出码,也不是curl错误码。

二、先确认哪个客户端、哪套信任库
浏览器正常而脚本报错,不足以证明服务端没问题,也不足以证明证书被吊销。两者可能走不同代理、不同入口,或使用不同信任库。先记录实际客户端版本与TLS后端,再确认URL、目标节点及运行账户。
openssl version
curl –version
本文实测命令行是OpenSSL 1.1.1k FIPS、curl 7.61.1的OpenSSL后端。这是复现实验环境,不是推荐部署的版本基线。生产应使用受支持的版本,并按实际后端查信任配置;不能把Linux文件路径原样套到所有系统。
若只有某个容器、服务账户或定时任务失败,应在它实际运行的环境读取CA配置,而不是以管理员终端成功作结论。诊断日志可能含请求头或内部域名,对外分享前脱敏。
三、分清发来的链与受信任的根
s_client的-showcerts展示的是服务器发来的证书列表,不是一条已经验证通过的链。不要把抓到的全部PEM直接放进受信任CA文件;对端不能通过多发一张证书,就替客户端决定该信谁。
私有PKI场景应从获授权的管理渠道取得根证书,独立核对指纹、用途与适用范围,再向需要它的客户端分发。下面只读取本地文件,不修改系统信任库;subject与issuer相同也不能单独证明其可信。
openssl x509 -in root.pem -noout \\
-subject -issuer -dates -fingerprint -sha256
指纹应与独立可信渠道提供的值比较,不是把同一份网络下载文件算两遍。文件名root.pem不带任何信用,下载成功更不是授权决定。公开网站则应核查是否部署了适合目标客户端的证书链,避免让普通访客安装来路不明的根。
四、把两种角色写进验证命令
以下假定已准备好leaf.pem、intermediate.pem和经授权确认的root.pem,叶证书SAN包含localhost。它们分别是待验证叶子、辅助构链的中间CA和信任锚;示例不是从未知服务抓证书后自动授信的脚本。
openssl verify -trusted root.pem \\
-untrusted intermediate.pem -purpose sslserver \\
-verify_hostname localhost -show_chain leaf.pem
在本文OpenSSL版本中,-trusted只使用指定信任锚,不再读取默认CA列表,且不能与-CAfile或-CApath混用。-untrusted补充构链材料,并不会因为放了根就让它受信任。命令还显式检查服务端用途与主机名,-show_chain用于观察实际构建路径。
需要的是“来源可信、角色正确、验证通过”三件事。离线OK不代表线上发链已经更新,下一步还必须连真实TLS终止点验收。
五、隔离实验:为什么删根只会换报错
本轮生成Root→Intermediate→localhost三层临时链,另建不相关的Other根作信任负例。所有离线验证使用明确的-trusted文件;另在127.0.0.1随机端口运行HTTPS服务,分别发送带根和不带根的链。未更改系统时间、系统信任或生产服务,结束销毁临时证书与私钥。
| 信Other;中间+根放untrusted | 19,depth 2 | 根出现但未获信任 |
| 信Other;仅提供中间 | 20,depth 1 | 找不到可用的上级 |
| 信Root;提供中间 | 退出0,OK | 路径到可信根 |
| 信Root;缺少中间 | 20,depth 0 | 构链材料仍不足 |
| 信Root;提供中间但验证错名 | 62,depth 0 | 信任不替代名称匹配 |
表中的失败案例进程均退出2,而不是退出19、20或62。HTTPS对照中,curl信Other时,带根返回60并报告self signed certificate in certificate chain;去掉根仍是60,只是变为unable to get local issuer certificate。两次均未进入HTTP处理。
改用正确Root后,两种发链方式都得到HTTP 200。这个结果不是鼓励生产发送根,而是证明删除根本身不能修复缺失的信任。实验只代表本机工具与夹具,不推断所有浏览器的构链策略。
六、修复后用真实请求验收
下面在Bash中执行,针对已准备的回环服务;8443替换为实际测试端口,root.pem为已确认的测试根。-q须放在首个选项以忽略默认curlrc,–noproxy仅用于本地实验,不是绕过生产网络策略的建议。
umask 077
rc=0
curl -q –noproxy '*' –cacert root.pem \\
–connect-timeout 3 –max-time 8 \\
–fail –silent –show-error –output body.txt \\
–write-out '%{http_code}' https://localhost:8443/ \\
> probe.status 2> probe.err || rc=$?
[ "$rc" -eq 0 ] || exit "$rc"
[ "$(<probe.status)" = 200 ] || exit 1
保留证书验证,不使用-k。包装器同时检查curl退出码和测试接口约定的200;HTTP错误、输出文件写失败、错误日志路径不可写都不能当成功。000表示未取得HTTP状态,不是服务端返回“000”。真实业务还需按接口契约验响应内容。
七、交付前最后一遍清单
- 明确失败账户、运行环境、TLS后端与实际终止点。
- 保留原始错误、depth、退出码和当时的证书指纹。
- 信任根来自独立授权渠道,辅助链不自动变为信任锚。
- 离线用途与名称检查通过,再做HTTPS和业务验证。
- 覆盖受影响客户端及节点,不拿一次成功代表全网。
遇到19,优先问“这个根为什么应该被我信任”,而不是“怎样把错误提示去掉”。证书链材料、信任策略和业务响应分开交付,修复才不会只停在一行OK。
八、参考资料
- OpenSSL verify:错误与信任参数
- OpenSSL s_client:showcerts边界
- curl:证书验证与CA来源
- curl命令手册
网硕互联帮助中心




评论前必须登录!
注册