3、证书轮换策略:轮换周期设计、蓝绿部署与金丝雀发布中的证书轮换、自动化轮换脚本
证书轮换这事儿,说白了就是跟时间赛跑。你想想看,一张证书从签发那天起,倒计时就开始了。我见过太多团队,证书过期前一晚才手忙脚乱地换,结果业务中断半小时。嗯,这一章咱们就把轮换策略聊透。
3.1 轮换周期设计:不是越短越好
轮换周期怎么定?我个人习惯先问三个问题:
- 证书被泄露的概率有多大?
- 轮换一次的成本有多高?
- 业务能接受多长的停机窗口?
答案不同,周期就不同。下面这张表是我在多个项目里总结出来的经验值:
| 场景 | 推荐周期 | 理由 |
|---|---|---|
| 内部服务 mTLS | 30 天 | 集群内证书流转快,自动化程度高 |
| 面向用户的 Web 证书 | 90 天 | CDN 缓存、客户端兼容性需要缓冲 |
| 根 CA / 中间 CA | 2-5 年 | 轮换成本极高,需手动审计 |
| IoT 设备证书 | 1 年 | 设备离线时间长,更新通道窄 |
注意: 我曾经遇到一个团队把内部证书周期设为 7 天,结果轮换脚本一次失败,整个集群的 gRPC 调用全断了。周期越短,对自动化可靠性的要求就越高。别为了「安全」盲目缩短周期。
3.2 蓝绿部署中的证书轮换
蓝绿部署的核心思路是「两套环境,一次切换」。证书轮换在这里天然有优势——你可以在绿环境里先换上新的证书,验证没问题了,再把流量切过去。
具体步骤我一般这么走:
- 准备绿环境:部署新版本应用,挂载新证书。
- 健康检查:用 curl 或 openssl 验证绿环境的 TLS 握手是否正常。
- 流量切换:从负载均衡器把流量从蓝切到绿。
- 观察期:保持蓝环境运行 24 小时,确认无异常后再回收。
- 回收旧证书:从蓝环境卸载旧证书,吊销或归档。
小技巧: 我习惯在蓝绿切换前,先做一次「证书预加载」。也就是在绿环境里提前 24 小时挂上新证书,但不对外暴露。这样能提前发现证书链不完整、OCSP 响应慢等问题。
3.3 金丝雀发布中的证书轮换
金丝雀发布比蓝绿更精细。你只替换一小部分实例的证书,观察业务指标,再逐步扩大范围。这种方式特别适合「证书变更可能影响客户端兼容性」的场景。
举个例子,有一次我们要把 RSA 证书换成 ECDSA 证书。ECDSA 的签名算法跟 RSA 不一样,有些老旧的客户端(比如 Android 4.x)可能不认。怎么办?
- 第一步:从 100 个实例里挑 2 个,挂上 ECDSA 证书。
- 第二步:监控这 2 个实例的 TLS 握手失败率、4xx/5xx 错误率。
- 第三步:如果指标正常,扩大到 10 个实例。
- 第四步:逐步推进到 50%、100%。
关键点: 金丝雀轮换必须搭配「灰度路由」。也就是说,只有特定比例的流量会命中新证书实例。我一般用 Istio 的权重路由或者 Nginx 的 split_clients 来实现。
3.4 自动化轮换脚本:从手动到无人值守
手动轮换证书?说实话,2025 年了,这不应该再发生。我写自动化脚本时,会遵循一个原则:脚本只做三件事——签发、部署、验证。
下面是一个基于 Bash + OpenSSL + systemd 的简化版轮换脚本。它适用于单机部署的场景:
#!/bin/bash
# cert-rotate.sh — 自动化证书轮换脚本
# 用法: ./cert-rotate.sh <域名> <证书路径>
set -euo pipefail
DOMAIN="${1}"
CERT_DIR="${2:-/etc/ssl/certs}"
DAYS_BEFORE_EXPIRY=30
# 1. 检查当前证书是否即将过期
CERT_FILE="${CERT_DIR}/${DOMAIN}.pem"
if [ -f "$CERT_FILE" ]; then
EXPIRY=$(openssl x509 -enddate -noout -in "$CERT_FILE" | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
NOW_EPOCH=$(date +%s)
REMAINING=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))
if [ "$REMAINING" -gt "$DAYS_BEFORE_EXPIRY" ]; then
echo "证书还有 ${REMAINING} 天过期,无需轮换"
exit 0
fi
fi
# 2. 签发新证书(这里假设有内部 CA)
openssl req -x509 -newkey rsa:2048 -keyout "${CERT_DIR}/${DOMAIN}.key" \
-out "${CERT_DIR}/${DOMAIN}.pem" -days 90 -nodes \
-subj "/CN=${DOMAIN}"
# 3. 部署新证书(重启服务)
systemctl reload nginx
# 4. 验证新证书是否生效
sleep 2
echo | openssl s_client -connect localhost:443 -servername "$DOMAIN" 2>/dev/null | \
openssl x509 -noout -dates
echo "证书轮换完成:${DOMAIN}"
注意: 上面的脚本只是演示。生产环境里,你还需要处理证书链、私钥权限、备份旧证书、发送告警通知。我踩过最大的坑是——脚本里忘了
set -euo pipefail,结果私钥生成失败,但脚本继续往下跑,最后 reload 了一个空证书,服务直接挂了。
3.5 轮换策略的整体流程
下面这张图,是我自己画的一个轮换策略总览。它把周期设计、蓝绿/金丝雀、自动化脚本串在了一起:
3.6 避坑指南
最后,分享几个我亲身踩过的坑:
- 证书链不完整:有一次轮换后,客户端报错「certificate unknown」。查了半天,发现新证书只发了叶子证书,没带中间 CA。从那以后,我每次轮换都会用
openssl verify -CAfile ca.pem cert.pem做完整链验证。 - 私钥权限:自动化脚本跑完,私钥文件权限变成 644,所有人都能读。嗯,这个低级错误我犯过一次。现在脚本里强制
chmod 600。 - 时钟偏移:证书的 notBefore 和 notAfter 是基于签发时间的。如果服务器时钟快了 5 分钟,新证书可能还没到生效时间。我建议在签发时加上
-offset -300(提前 5 分钟生效)。 - 回滚预案:自动化轮换脚本必须带回滚能力。我习惯在部署新证书前,先备份旧证书到
/var/backup/certs/,并保留最近 3 个版本。
我的习惯: 每次轮换完成后,我会手动跑一遍
openssl s_client -connect <host>:443 -showcerts,确认证书链、有效期、SNI 都正常。虽然自动化了,但关键步骤还是看一眼放心。