浏览器不报错的证书,为什么到了 curl 和 App 里就握手失败:中间证书与证书链

服务器只配了终端实体证书而漏掉中间证书,是最常见的 TLS 部署事故之一。浏览器靠 AIA 拉取补链掩盖了问题,curl、Java 客户端和 App 却会直接握手失败。本文解释证书链如何工作、何时踩坑、怎么排查。

本文目录
浏览器不报错的证书,为什么到了 curl 和 App 里就握手失败:中间证书与证书链

线上有个很典型的怪现象:运维同事在浏览器里打开站点,锁图标好好的,SSL Labs 扫一遍也绿油油。结果监控脚本里的 curl 报 unable to get local issuer certificate,安卓 App 直接连不上。两边看的明明是同一个域名、同一张证书,谁也没说谎——问题出在证书链上,而浏览器悄悄替你把坑填平了。

链是怎么一回事

CA 几乎不会用根证书直接签你的网站证书。根证书私钥通常离线保存,日常签发用的是中间证书(intermediate CA)。所以一张网站证书背后通常是这样的层级:

根 CA(在客户端信任库里,不下发)
  └── 中间 CA(需要服务器随证书一起下发)
        └── 你的终端实体证书(leaf / end-entity)

TLS 握手时,服务器应该把终端实体证书和中间证书一起发过去,客户端拿着这条链逐级验签,最后对上自己信任库里的根。根证书本身不需要发,发了也会被忽略——客户端只信自己库里有的那份。

浏览器不报错的证书,为什么到了 curl 和 App 里就握手失败:中间证书与证书链 illustration 1
浏览器不报错的证书,为什么到了 curl 和 App 里就握手失败:中间证书与证书链 illustration 1查看原图

为什么浏览器没事,别的客户端全挂

很多中间证书里带一个 AIA(Authority Information Access)扩展,里面写着"去哪个 URL 下载签发者证书"。主流桌面浏览器会做 AIA fetching:发现链断了,自己去把中间证书下载下来补上。所以 Chrome 里一切正常,问题被藏得很好。

curl、OpenSSL、Java 的默认 TLS 栈、大多数移动端 HTTP 客户端不做这种补链。链断了就是断了,握手失败。这就是为什么这类事故总是"部分用户正常、部分用户炸",排查时还很容易被误判成客户端版本问题。

Nginx 配置里最常见的错法是把证书文件指向只有终端实体证书的那一个文件,而不是 fullchain:

ssl_certificate     /etc/nginx/certs/example.com.crt;        # 只有 leaf,链不完整
ssl_certificate     /etc/nginx/certs/example.com-fullchain.crt;  # leaf + 中间证书
浏览器不报错的证书,为什么到了 curl 和 App 里就握手失败:中间证书与证书链 illustration 2
浏览器不报错的证书,为什么到了 curl 和 App 里就握手失败:中间证书与证书链 illustration 2查看原图

用 OpenSSL 可以快速确认服务器实际下发了什么:

openssl s_client -connect example.com:443 -servername example.com -showcerts

看输出里有几张证书。如果只有一张,或者出现 verify error:num=21:unable to verify the first certificate,基本就是中间证书没下发。

什么时候会撞上它

  • 从 CA 后台手动下载证书时,选错了文件,只拿了终端实体证书。
  • 换 CA 或续期后,中间证书变了,但配置里拼接的还是旧的那份。Let's Encrypt 就同时存在 RSA 和 ECDSA 两条链,混着拼会导致一半客户端验不过。
  • 内部 PKI 场景:自建的中间 CA 不在任何公共信任路径上,AIA 也未必可用,所有客户端都依赖服务器正确下发。
  • 客户端证书固定(pinning)的 App:pin 的是中间证书的话,CA 换链就直接大面积断连,这类坑最好在 pin 策略阶段就想清楚。
浏览器不报错的证书,为什么到了 curl 和 App 里就握手失败:中间证书与证书链 illustration 3
浏览器不报错的证书,为什么到了 curl 和 App 里就握手失败:中间证书与证书链 illustration 3查看原图

还有个反直觉的点:证书文件里多塞几张"没用"的证书不会报错,但会白白增大握手报文。链文件只保留"leaf + 到根为止所需的中间证书",别把根也拼进去凑数。

在 CertbotX 中实践

ACME 自动签发的流程天然避开了"选错证书文件"这类手工失误:CertbotX 在签发后会把完整链作为部署内容的一部分,自动推送到 Agent、EdgeOne 或 CDN,不需要人工拼接 fullchain。续期也是到期前自动重新签发并再次部署,链里中间证书变化时会一并更新,不会出现"新证书配旧链"的组合。每次部署留有记录和审计信息,出问题时能直接回看当时下发的是哪条链,而不是靠回忆。

核验与延伸阅读

← 返回资讯中心