证书明明没到期,部分用户却打不开网站:一次中间证书缺失故障复盘

复盘一起典型的 TLS 故障:证书在有效期内,桌面浏览器正常,但部分移动端和 API 客户端报证书错误。根因是服务器漏发中间证书。记录排查过程、为什么问题具有迷惑性,以及如何用自动化签发部署避免重蹈覆辙。

本文目录
证书明明没到期,部分用户却打不开网站:一次中间证书缺失故障复盘

周五下午客服群里冒出一条消息:有用户反映 App 里打不开我们的页面,提示"证书不受信任"。运维同事第一反应是查证书到期时间——还有 70 多天。自己拿 Chrome 打开,一切正常,小绿锁安安静静的。

这就有意思了。

症状比看起来更分裂

故障的表现并不统一:

  • 桌面 Chrome、Edge、Safari 全部正常。
  • 一部分 Android 手机报错 NET::ERR_CERT_AUTHORITY_INVALID。
  • 调用我们 API 的合作伙伴那边,Java 客户端抛出 PKIX path building failed。
  • curl 在某些机器上正常,在另一些机器上直接拒绝。

这种"时好时坏"的现象最容易把人带偏,以为是 CDN 节点配置不一致,或者客户端系统太旧。但这次都不是。

根因:服务器只发了一半的证书链

用 openssl s_client -connect example.com:443 -servername example.com 看一眼就清楚了:服务器只返回了一张叶子证书,没有附带中间证书。

证书明明没到期,部分用户却打不开网站:一次中间证书缺失故障复盘:an ACME request being validated at separate stations and turned into a new deployable artifact while private key material remains at its origin
证书明明没到期,部分用户却打不开网站:一次中间证书缺失故障复盘:an ACME request being validated at separate stations and turned into a new deployable artifact while private key material remains at its origin查看原图

TLS 握手时,服务器应该把"叶子证书 + 中间证书"一起发给客户端,客户端自己拿着根证书去验证这条链。浏览器为什么没挂?因为 Chrome 和 Firefox 会缓存中间证书,也能通过证书里的 AIA 扩展自己去把缺的中间证书下载回来补齐。多数桌面用户访问过别的站点,缓存里早就有了那张中间证书,所以毫无感知。

但 Android 的部分 WebView、Java 的默认 TLS 栈、以及不少精简的 HTTP 客户端,不做 AIA 补齐,缓存也是空的。链断了就是断了,直接报错。

换句话说,浏览器的"宽容"掩盖了服务器的配置错误,让故障看起来像客户端的问题。这就像酒店前台把少填一行的登记表放进了抽屉——常客能住进去,是因为大堂经理认识你,不是因为表格没问题。

排查时的验证手段很简单:

  • openssl s_client 的输出里看 Certificate chain 一段,数一下返回了几张证书。
  • 或者用 SSL Labs 的在线检测,它会直接标出 "Chain issues: Incomplete"。

修复也不难:把签发机构给的 fullchain(叶子 + 中间)完整部署到服务器。以 Nginx 为例,ssl_certificate 指向的文件必须包含完整链,而不是只有那张孤零零的域名证书。改完 reload,再用 openssl 确认链完整。

为什么这种事会反复发生

这次事故的根源不在"忘了复制文件"这种偶然失误,而在流程:证书是手动下载、手动拼接、手动传的。Let's Encrypt 签出来的文件里有 cert.pem 和 fullchain.pem 之分,一字之差,效果天差地别。只要流程里有一步靠人记,迟早有人会记错,而且浏览器还会帮忙打掩护,让错误潜伏几周甚至几个月。

证书明明没到期,部分用户却打不开网站:一次中间证书缺失故障复盘:a new certificate version moving from its source to several edge appliances while the previous working version remains ready for rollback
证书明明没到期,部分用户却打不开网站:一次中间证书缺失故障复盘:a new certificate version moving from its source to several edge appliances while the previous working version remains ready for rollback查看原图

另外值得留意:中间证书不是一成不变的。CA 会轮换中间证书,上次手动拼的链,续期之后可能就过时了。每次续期都应该重新部署完整的链文件,而不是复用旧的中间证书。

预防:把"拼链"从人的待办清单里删掉

靠谱的预防就两条:

一是让签发和部署全自动完成,客户端直接用 CA 返回的完整链文件,中间不经过手工拼接。二是在每次部署后加一道自动化校验——用 openssl 或在线检测确认链完整,发现问题在上线前就拦住,而不是等 Android 用户来报。

在 CertbotX 中实践时,ACME 自动签发拿到的就是 CA 返回的完整链,续期时同样重新拉取,不存在"复用旧中间证书"的坑;部署到 Agent、EdgeOne 或 CDN 时推送的也是完整链文件。每次部署都有记录可查,审计时能看清哪台机器在哪天换上了哪张证书,出问题回滚和定位都快得多。

收尾

这次故障从用户报错到定位修复花了不到两小时,但"部分用户受影响、桌面一切正常"的假象,让它在报障前可能已经存在了好几天。教训很朴素:TLS 配置的正确性,不能拿浏览器当裁判。浏览器的容错是用户体验上的好事,对运维却是一种温柔的麻痹。

想自己核验链是否完整,可以读一下 RFC 8555 中 ACME 客户端下载证书的说明(RFC 8555 第 7.4.2 节),以及 Nginx 官方对 ssl_certificate 链文件要求的文档(Nginx HTTPS 服务器配置)。

← 返回资讯中心