证书链缺中间证书:浏览器能打开,API 却握手失败
服务器只下发叶子证书、漏掉中间证书时,桌面浏览器常靠 AIA 抓取自动补齐,掩盖了配置错误;而命令行工具、移动端和部分 TLS 库会直接握手失败。文章分析这一隐蔽风险的成因、排查方法与部署时的防范措施。
本文目录

线上出现过这种工单:用户反馈网站正常,合作方的接口调用却全部报证书错误。运维在浏览器里看,锁标志好好的,证书也在有效期内,结论一度写成"对方网络问题"。实际原因很常见——服务器只配置了叶子证书,没把中间证书一起发出去。
浏览器在替你掩盖错误
TLS 握手中,服务器应该把从叶子到中间 CA 的完整证书链(不含根)发给客户端。很多桌面浏览器遇到链不完整时,会根据叶子证书里的 AIA(Authority Information Access)扩展自己去下载中间证书,还会把下载过的中间证书缓存起来。结果就是:你测试的浏览器一切正常,换一台没缓存过该中间证书的设备、或用 curl、Java、部分移动端的 TLS 栈,握手直接失败,报"unable to get local issuer certificate"之类。
这类问题最难缠的地方在于它不稳定:同一台服务器,有人能访问有人不能,故障看起来"随机",很容易被误判成网络抖动。

常见的踩坑方式
- 证书文件里只有
cert.pem,没用fullchain.pem。Let's Encrypt 等 ACME 签发的产物里,fullchain.pem才是应该配进服务器的那个。Nginx 的ssl_certificate指向只含叶子的文件,就是典型事故配置。 - 证书是从面板或邮件里手动粘贴的,只粘了第一段。
- 换 CA 或中间证书轮换后,旧中间证书还留在链里,新旧混发。
- 负载均衡或 CDN 回源侧配了完整链,但某个直连入口(管理后台、监控探测口)漏了。
排查不复杂:
openssl s_client -connect example.com:443 -servername example.com
看输出里的证书链有几段。只有一段就说明中间证书没发。再顺手 curl -v https://example.com 在几台干净机器上验证,比浏览器可靠——浏览器太宽容了,宽容得像一位从不指出你错别字的朋友。
部署环节的根治办法
手工维护证书文件,链不完整几乎是早晚的事,因为"签发"和"拼装下发"是两个动作,后者没有自动校验。处置建议:
- 服务器配置固定引用包含完整链的文件,不要在部署脚本里做手工拼接。
- 续期流程自动化后,把链完整性检查并入部署后的自检:部署完用 openssl 验证链段数,不完整就回滚告警。
- 监控探测不要只用内网或缓存过的客户端,至少有一条从外部、干净 TLS 环境发起的 HTTPS 探测。

在 CertbotX 中实践:通过 ACME 自动签发的证书天然以完整链形式产出,续期在到期前自动完成,并可直接推送到 Agent、EdgeOne 或 CDN 等部署目标,避免"签发一份、手工贴一半"的中间环节。每次部署都会留下记录,审计时能看清哪个入口跑的是哪一版链,出问题不必靠回忆排查。
另一个顺带的提醒:链发多了也有小坑——把根证书也塞进服务器链没有安全收益,只会让每个握手多传一点字节。链的边界就是"叶子 + 所有中间证书,到根为止、不含根"。
核验与延伸阅读