证书链缺了中间证书:浏览器能开、API 调用却大面积报错的原因和处置
服务器只部署了叶子证书而漏掉中间证书,桌面浏览器靠缓存和 AIA 抓取悄悄补上,命令行客户端却直接握手失败。本文解释这一现象的成因、排查方法和部署时的正确做法。
本文目录

线上有个挺折磨人的现象:运维同事说证书明明刚换好,自己浏览器打开一切正常,结果监控告警和移动端的报错刷屏——certificate verify failed。两边都没说谎,问题出在证书链缺了中间证书,而浏览器替你"补考及格"了。
为什么浏览器能打开
TLS 握手时,服务器应把叶子证书和中间证书一起发给客户端。很多签发流程里,运维只拿到 domain.crt 这一个文件,直接配上去了,中间证书没发。

桌面浏览器对此很宽容:一是它会缓存中间证书——只要你访问过同一家 CA 签发的其他站点,中间证书已经在本地了;二是现代证书里带 AIA(Authority Information Access)扩展,里面写了中间证书的下载地址,Chrome 会自己去抓。抓到了,链就补上了,地址栏一片祥和。
但 OpenSSL、curl、Java 的默认 truststore、很多移动端 HTTP 库不做 AIA 抓取,也不一定有那份缓存。它们拿到一条断掉的链,只能报错。于是同一个站点,浏览器是绿的,脚本全线飘红。
排查方法
最直接的验证:

openssl s_client -connect example.com:443 -servername example.com看输出里的 Verify return code。如果是 21 (unable to verify the first certificate),基本就是缺中间证书。也可以在返回的证书列表里数一下:只返回了一张证书(叶子),中间证书缺席。
注意 -servername 别省。一个 IP 上挂多个站时,不带 SNI 测到的可能是默认证书,查错了对象更浪费时间。

另外有个坑:某些 CDN 或负载均衡后面有多台节点,如果只有部分节点配置正确,用 openssl 抽查一次可能刚好命中好的那台。出问题时间歇性发作的,多测几次,或者逐节点测。
正确的部署姿势
多数 CA 会提供 fullchain.pem(叶子 + 中间),或者让你自己拼接。拼接顺序是从叶子到根方向的中间证书,叶子在前:
cat domain.crt intermediate.crt > fullchain.pem
根证书不用放进去,客户端信任库里本来就有,发了也是白占握手流量。
在 CertbotX 中实践:自动签发流程拿到的就是完整的 fullchain,重新签发后自动部署到 Agent、EdgeOne 或 CDN 时也是整条链一起推,不存在"只传了叶子证书"这种半成品状态。部署记录里能看到每次推送的目标和结果,排查"哪个节点没更新"这类问题时不用挨个登录机器翻文件。
顺手要查的两件事

修链的同时值得顺带看一眼:
- 中间证书本身也会过期。Let's Encrypt 这类 CA 偶尔会轮换中间证书。如果你的部署脚本里硬编码了某张中间证书文件,签发方换链后你还在发旧的,又会断。每次签发都用新下发的链,别复用。
- 交叉签名的兼容性。有些 CA 提供多条链(比如同时兼容老 Android 的交叉签名链)。如果你的客户端里有老旧设备,确认发的是哪条链,必要时按 CA 文档选择。
证书链这事的小坑在于:它不报错的时候你根本不知道它断了。上线新证书后,别只用浏览器点一下就算验收——拿 openssl s_client 或者 SSL Labs 的检测跑一遍,把"运气"从验收流程里剔除。