浏览器能开、curl 却报证书错:聊聊中间证书和证书链
服务器只配了叶子证书、漏掉中间证书时,浏览器常能靠缓存补上,而 curl、API 客户端会直接失败。本文解释证书链的构成、为什么表现不一致,以及何时会遇到这个问题。
本文目录

有个很经典的现场:运维说网站 HTTPS 没问题,自己浏览器打开好好的,锁头也在。结果合作方的接口调用方报 certificate verify failed,或者监控探针告警。两边都没说谎,问题多半出在中间证书上。
证书不是一张,是一串
CA 很少用根证书直接签你的站点证书。真实结构是:根证书 → 中间证书 → 你的叶子证书。验证时,客户端要从叶子一路走到自己信任的根,这条路叫证书链(chain of trust)。

关键点在于:服务器的责任是把叶子证书和中间证书一起发出去。根证书不用发,客户端本地信任库里就有。TLS 握手时服务器按顺序把这条链(不含根)丢给客户端,客户端才能拼出完整路径。
很多新手拿到 CA 发的文件,只把站点证书塞进 Nginx 的 ssl_certificate,把中间证书文件丢在一边。握手时客户端只收到一片叶子,找不到通往根的路,验证失败。
那为什么浏览器没事?

因为浏览器会"补课"。桌面浏览器缓存了大量常见中间证书(你访问过同 CA 的其他站点时就缓存下来了),有些还支持通过证书里的 AIA(Authority Information Access)扩展去主动下载缺失的中间证书。所以链缺一节,Chrome 可能自己补齐,页面照常打开。
curl、OpenSSL、Python 的 HTTP 库、大多数 API 客户端没这个耐心:链不完整就是失败。于是就出现了"浏览器正常、程序报错"这种让人怀疑人生的现象。
什么时候会撞上它
- 换 CA 或续期后,新证书用的中间证书变了,服务器配置没更新。
- 同一个域名,浏览器测试通过,但移动端 App、第三方支付回调、监控探针集体报错。
- 内网系统用私有 CA,客户端信任库里只有根,服务器又没发中间证书。

排查也简单:openssl s_client -connect example.com:443 -servername example.com 看输出的证书链有几张;或者用 SSL Labs 的在线检测,它会直接告诉你 "chain issues: incomplete"。修复方法就是把中间证书按正确顺序拼接进服务器配置的证书文件(叶子在前,中间在后),而不是指望客户端自己想办法。
在 CertbotX 中实践
用 ACME 协议(如 DNS-01 配合 CNAME 委托)自动签发时,签发服务返回的就是完整链,CertbotX 在自动部署到 Agent、EdgeOne 或 CDN 时会带上全链,部署记录可审计,到期前重新签发也会同步更新链文件——少了人工拼接这一环,也就少了"只更新叶子忘更新中间证书"这类经典翻车。

一个容易混淆的邻居:交叉签名
有时一张中间证书会被多个根交叉签名(比如为了兼容老设备),链可能出现多条路径。这时客户端走哪条路都行,但服务器仍然要把其中一条完整路径发出来。链不完整和交叉签名是两回事,别混着排查。
延伸阅读: