中间证书丢了会怎样:浏览器能开、curl 却报错的证书链问题

服务器只发叶子证书而不带中间证书时,浏览器常因缓存了中间证书而显示正常,curl、Java 客户端却会握手失败。本文解释证书链的构成、为什么不同客户端表现不一致,以及如何排查和修复。

本文目录
中间证书丢了会怎样:浏览器能开、curl 却报错的证书链问题

线上最常见的一种"灵异"TLS 故障长这样:浏览器打开网站一切正常,锁标志也在,但监控系统、curl 或者某个 Java 服务调用同一个域名就报 unable to get local issuer certificate。运维第一反应往往是"客户端有问题",其实锅通常在服务端——证书链没配全,少了中间证书。

证书链到底是什么

中间证书丢了会怎样:浏览器能开、curl 却报错的证书链问题:an ACME request being validated at separate stations and turned into a new deployable artifact while private key material remains at its origin
中间证书丢了会怎样:浏览器能开、curl 却报错的证书链问题:an ACME request being validated at separate stations and turned into a new deployable artifact while private key material remains at its origin查看原图

CA 不会拿根证书直接给你的域名签发。根证书私钥被锁得很严实,日常签发用的是中间 CA 的证书。所以一条完整的信任链通常是:

  • 你的叶子证书(域名证书)
  • 一张或多张中间证书
  • 根证书(预置在操作系统或浏览器的信任库里)

TLS 握手时,服务器应该把叶子证书和所有中间证书一起发给客户端。根证书不用发,客户端自己就有,发了也没用。

中间证书丢了会怎样:浏览器能开、curl 却报错的证书链问题:a new certificate version moving from its source to several edge appliances while the previous working version remains ready for rollback
中间证书丢了会怎样:浏览器能开、curl 却报错的证书链问题:a new certificate version moving from its source to several edge appliances while the previous working version remains ready for rollback查看原图

为什么浏览器"看起来没问题"

现代浏览器会缓存访问过的网站发过来的中间证书,也会通过证书里的 AIA(Authority Information Access)扩展去 CA 的地址把缺失的中间证书下载回来。所以即使你的服务器只发了叶子证书,Chrome 往往也能自己把链补全,用户毫无感知。

curl、OpenSSL、Java、Python 的多数 HTTP 库没这么勤快。它们拿到一张叶子证书,本地信任库里只有根,中间缺一环,链构不出来,直接报错。

中间证书丢了会怎样:浏览器能开、curl 却报错的证书链问题:an approaching certificate expiry being detected early, followed by a fresh request and a safe replacement before the live connection is interrupted
中间证书丢了会怎样:浏览器能开、curl 却报错的证书链问题:an approaching certificate expiry being detected early, followed by a fresh request and a safe replacement before the live connection is interrupted查看原图

这就是为什么"浏览器能开"不能作为证书配置正确的判据。用 openssl s_client -connect example.com:443 -servername example.com 看握手输出里的 Verify return code,或者数一数服务器返回了几张证书,才是靠谱的验证方式。

怎么修

大多数 Web 服务器要求你把叶子证书和中间证书按顺序拼接成一个文件:叶子在前,中间证书依次在后。Let's Encrypt 的客户端直接给你两个文件:cert.pem 只有叶子,fullchain.pem 才是拼好的完整链。Nginx 的 ssl_certificate 指向的应该是 fullchain.pem,指成 cert.pem 就是这个故障的典型成因。

中间证书丢了会怎样:浏览器能开、curl 却报错的证书链问题:a browser-side endpoint independently inspecting the live TLS connection after deployment and comparing the observed endpoint artifact
中间证书丢了会怎样:浏览器能开、curl 却报错的证书链问题:a browser-side endpoint independently inspecting the live TLS connection after deployment and comparing the observed endpoint artifact查看原图

Apache 旧版本有单独的 SSLCertificateChainFile 指令,新版本也推荐直接合并进 SSLCertificateFile。各家的写法略有差异,但原则一致:服务器要发送叶子加全部中间证书。

改完配置记得重新握手验证一次,别只看浏览器。

在 CertbotX 中实践

中间证书丢了会怎样:浏览器能开、curl 却报错的证书链问题:an operator tracing a failed certificate delivery path while a parallel known-good route keeps serving users until recovery is verified
中间证书丢了会怎样:浏览器能开、curl 却报错的证书链问题:an operator tracing a failed certificate delivery path while a parallel known-good route keeps serving users until recovery is verified查看原图

CertbotX 通过 ACME 自动签发证书时,拿到并部署的是包含中间证书的完整链,部署到 Agent、EdgeOne 或 CDN 的记录会留档可审计。对多节点部署的场景,这能避免某台机器上手工拼接漏掉中间证书、却又因为浏览器表现正常而长期无人发现的情况。证书到期前的自动重新签发同样按完整链处理。

核验参考

← 返回资讯中心