一次SSL证书过期导致内网HTTPS服务全面中断的排查实录

问题背景

某周四早上 8:30,帮助台工单系统开始涌入大量相同类型的工单:员工反映多个内网系统无法访问,浏览器提示”您的连接不是私密连接”或”证书无效”,点击”高级”可以看到错误信息 NET::ERR_CERT_DATE_INVALID

受影响的系统包括:内网 GitLab、Jira、Confluence、Jenkins、内部文档管理系统等约 12 个 HTTPS 服务,全部托管在内网环境中。


故障现象

  • 浏览器打开内网 HTTPS 地址时报 SSL 证书错误:NET::ERR_CERT_DATE_INVALID
  • Chrome 错误详情:服务器证书已过期,证书有效期显示截止日期为昨日
  • 影响约 12 个内网 HTTPS 服务
  • 使用 HTTP(非 HTTPS)的内网服务不受影响
  • 外网面向公众的 HTTPS 服务(Let’s Encrypt 证书)不受影响

排查过程

第一步:确认证书过期情况

用 openssl 工具快速检查几个受影响服务的证书:

1
echo | openssl s_client -connect gitlab.internal.company.com:443 -servername gitlab.internal.company.com 2>/dev/null | openssl x509 -noout -dates

输出:

1
2
notBefore=Sep  5 00:00:00 2021 GMT
notAfter=Sep 4 23:59:59 2024 GMT

证书昨日(2024-09-04)到期,今天(2024-09-05)证书已失效。

再检查其他几个受影响的服务:

1
2
3
4
for host in gitlab jira confluence jenkins docs; do
echo -n "$host.internal.company.com: "
echo | openssl s_client -connect "$host.internal.company.com:443" 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null
done

输出:

1
2
3
4
5
gitlab.internal.company.com:       notAfter=Sep  4 23:59:59 2024 GMT
jira.internal.company.com: notAfter=Sep 4 23:59:59 2024 GMT
confluence.internal.company.com: notAfter=Sep 4 23:59:59 2024 GMT
jenkins.internal.company.com: notAfter=Sep 4 23:59:59 2024 GMT
docs.internal.company.com: notAfter=Sep 4 23:59:59 2024 GMT

所有受影响的服务证书到期日期完全一致:2024年9月4日。这说明这 12 个服务使用的是同一张证书,而不是各自独立的证书。

第二步:确认证书类型

1
echo | openssl s_client -connect gitlab.internal.company.com:443 2>/dev/null | openssl x509 -noout -text | grep -A5 "Subject Alternative Name"

输出:

1
2
3
X509v3 Subject Alternative Name: 
DNS:*.internal.company.com
DNS:internal.company.com

这是一张 内网通配符证书(Wildcard Certificate),覆盖所有 *.internal.company.com 的子域名。证书由内部 CA(根证书已部署到公司所有终端的信任链)签发,3 年有效期,上一次签发是 2021 年 9 月,今天正好到期。

第三步:为什么没有收到到期预警?

翻查监控系统配置,发现:

  1. Zabbix 中确实有 SSL 证书到期监控项,但只对公网域名www.company.comapi.company.com 等)进行监控,内网域名没有加入监控列表
  2. 上一任运维工程师(已离职)当时签发这张证书时,没有在日历中设置到期提醒,也没有记录在运维手册中
  3. 邮件告警脚本存在,但 3 年前配置后从未验证过是否真正发出了告警

第四步:找到内部 CA

通过证书信息找到签发这张证书的内部 CA:

1
echo | openssl s_client -connect gitlab.internal.company.com:443 2>/dev/null | openssl x509 -noout -issuer

输出:

1
issuer=C=CN, O=Company Internal CA, CN=Company Root CA G1

查找 CA 证书和私钥的存放位置,询问了老员工,最终找到保存在一台运维管理服务器(mgmt-srv)上的 /etc/pki/internal-ca/ 目录下:

1
2
ls /etc/pki/internal-ca/
# ca.crt ca.key ca.srl issued/

确认内部 CA 私钥存在且有效。

第五步:重新签发证书

重新生成一张 3 年有效期的通配符证书,这次改为 3 年并在交接文档中记录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 生成新的私钥
openssl genrsa -out /tmp/wildcard-internal.key 2048

# 生成 CSR(证书签名请求)
cat > /tmp/san.cnf << 'EOF'
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
[req_distinguished_name]
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = *.internal.company.com
DNS.2 = internal.company.com
EOF

openssl req -new -key /tmp/wildcard-internal.key \
-out /tmp/wildcard-internal.csr \
-subj "/C=CN/O=Company/CN=*.internal.company.com" \
-config /tmp/san.cnf

# 使用内部 CA 签发(有效期 3 年)
openssl x509 -req -in /tmp/wildcard-internal.csr \
-CA /etc/pki/internal-ca/ca.crt \
-CAkey /etc/pki/internal-ca/ca.key \
-CAcreateserial \
-out /tmp/wildcard-internal.crt \
-days 1095 \
-extensions v3_req \
-extfile /tmp/san.cnf

# 验证新证书
openssl x509 -noout -dates -in /tmp/wildcard-internal.crt

输出:

1
2
notBefore=Sep  5 02:10:00 2024 GMT
notAfter=Sep 4 02:10:00 2027 GMT

第六步:更新各服务的证书

将新证书分发到所有受影响的服务器,并替换旧证书:

1
2
3
4
5
6
# 复制到各服务器(以 Nginx 为例)
scp /tmp/wildcard-internal.crt user@gitlab-server:/etc/nginx/ssl/internal.crt
scp /tmp/wildcard-internal.key user@gitlab-server:/etc/nginx/ssl/internal.key

# 重载 Nginx
ssh user@gitlab-server "nginx -t && nginx -s reload"

对所有 12 个服务逐一执行。约 40 分钟后,全部服务恢复正常。


解决方案总结

  1. 重签内部通配符证书(有效期至 2027-09-04)
  2. 更新所有 12 个内网 HTTPS 服务的证书文件并重载
  3. 在 Zabbix 中添加对内网域名的 SSL 证书到期监控(30 天、14 天、7 天分级告警)
  4. 建立证书台账,记录所有证书的域名、有效期、存放位置、签发时间

根因分析

根本原因是证书生命周期管理缺失:内网证书的签发、更新、到期预警没有纳入规范化的管理流程。负责人离职后,这段”历史知识”彻底断层,直到证书真正过期才被动发现。


预防措施

1. 全面的证书到期监控

1
2
3
4
5
6
7
8
9
10
# Zabbix 自定义监控项(检查证书到期天数)
#!/bin/bash
DOMAIN=$1
PORT=${2:-443}
DAYS=$(echo | openssl s_client -connect "$DOMAIN:$PORT" 2>/dev/null \
| openssl x509 -noout -enddate 2>/dev/null \
| sed 's/notAfter=//' \
| xargs -I{} date -d "{}" +%s \
| xargs -I{} bash -c 'echo $(( ($1 - $(date +%s)) / 86400 ))' - {})
echo $DAYS

将所有内网和外网 HTTPS 域名加入监控。

2. 建立证书台账

在内部 Wiki 或文档管理系统中记录所有证书信息,包括到期时间,设置日历提醒(提前 90 天、30 天各提醒一次)。

3. 考虑迁移到 ACME 协议自动续签

对内网环境,可以搭建内部 ACME 服务(如 step-ca),实现证书自动续签,从根本上消除手动管理的风险。


总结

证书管理是一项容易被遗忘但后果严重的运维工作。3 年有效期的证书,在签发后会很快淡出运维视野,直到某天突然”爆炸”。解决之道不是靠记忆力,而是靠自动化监控和标准化流程

一次简单的证书过期,影响了 12 个系统、全公司近 300 名员工的工作效率,教训深刻。