证书悄悄过期:一次 HTTPS 服务雪崩的生产复盘

证书悄悄过期:一次 HTTPS 服务雪崩的生产复盘

一、故障开始的那几分钟

周三凌晨 02:14,监控告警集中爆发:支付回调失败率从 0 飙到 100%,App 端大面积提示“网络异常”。但服务器 CPU、内存、数据库连接数全都正常——典型的“服务在线、业务挂掉”。

最先定位到异常的是网关日志里密密麻麻的 SSL handshake failed。一查证书有效期,根因一目了然:核心域名的两张证书在前一天夜里 23:59 同时到期,而我们一直以为它们还有 30 天——因为监控看的是另一张早已废弃的旧证书。

二、为什么“还有 30 天”是假的

这是一个很典型的认知偏差:

  • 监控对象错了。我们的证书过期告警绑定的是测试环境的一张证书,生产域名的证书从来没进过监控列表。
  • 多证书并存。同一个域名历史上换过 CDN 厂商,旧的证书文件残留在服务器上,运维脚本扫到的恰好是那张“还活着”的旧证。
  • 中间证书缺失。即便主证书续上了,部署时漏了中间证书(Intermediate CA),部分老浏览器和移动端直接判定链不完整,表现为“部分用户能访问、部分不行”。

这三件事叠加,让“还有 30 天”的错觉维持了整整一个月。

三、止血与恢复

  1. 立即回滚到上一版证书(CDN 侧有历史版本),先让流量恢复。
  2. 补发完整证书链:用 fullchain 而非单张 cert,避免中间证书缺失。
  3. 逐端验证:不止 curl,还要用真机(iOS / Android / 旧版 Chrome)打开核心页面,确认没有 NET::ERR_CERT_*。
  4. 临时加宽告警窗口:在到期前 30/14/7/3/1 天分档告警,直到自动化接管。

从告警爆发到全量恢复,耗时约 47 分钟——其中 30 分钟花在“为什么线上和本地表现不一致”的排查上。

四、把这次故障变成规则

复盘后我们落地了几条硬规矩:

  • 证书监控必须按“域名 + 部署位置”双维度登记,而不是只看某一张文件。
  • 续证脚本必须做链完整性校验,签发后用 openssl verify -CAfile 自测,失败不许上线。
  • 每次部署后跑一次端到端 TLS 探针,把“真机可访问”作为发布门的硬性条件。
  • 证书到期前 30 天起,进入“盯防期”,钉钉/邮件双通道提醒,到期前 7 天未续则升级到值班负责人。

五、可以顺手做的小事

如果你也用 Let's Encrypt,把续期做成定时任务只是第一步。更稳的做法是:续期成功后自动触发“部署 + 探针 + 通知”一条龙,而不是等人工发现到期。证书本身免费,但因为它导致的那次雪崩,代价远超想象。

posted @ 2026-11-09 09:34  jiunotes  阅读(1)  评论(0)    收藏  举报