HTTPS 证书过期是运维工程师最不愿意面对但又最容易忽视的问题之一。证书过期后,用户访问网站时会看到浏览器显示的警告页面,直接影响业务可用性和用户信任度。更严重的是,某些场景下证书过期可能导致服务完全不可用。要知道,这可是实打实的线上事故。
本文系统讲解 HTTPS 证书的工作原理、证书过期的风险、监控方案以及自动化管理实践。内容涵盖了 Let's Encrypt 的申请与续期、主流证书管理工具、以及如何搭建一套可靠的监控告警体系。希望能帮你彻底摆脱证书过期带来的噩梦。
一、HTTPS 证书基础
1.1 加密通信的原理
HTTPS(HTTP Secure)是 HTTP 协议的安全版本,通过 TLS(Transport Layer Security)协议实现加密通信。
TLS 1.3 是当前最新的主推版本,于 2018 年发布,相比 TLS 1.2 有显著的握手性能提升和安全性增强。
加密通信的核心依赖于公钥密码学:
- 服务器持有公钥和私钥,公钥用于加密,私钥用于解密。
- 客户端从服务器的数字证书中获取公钥。
- 客户端生成一个随机的会话密钥,并用服务器的公钥加密后发送给服务器。
- 服务器用自己的私钥解密,拿回会话密钥。
- 之后的通信全部使用这个会话密钥进行高效的对称加密。
1.2 数字证书的结构
数字证书就像是服务器的电子身份证,里面包含了这些关键信息:
- 证书持有者信息:域名、公司名称等。
- 颁发者信息:证书颁发机构(CA)的信息。
- 公钥信息:证书持有者的公钥。
- 有效期:证书的生效时间和失效时间。
- 序列号:CA 分配的唯一的证书编号。
- 签名:CA 对证书内容做的数字签名。
你可以用下面的命令来查看证书的详细信息:
# 查看证书的详细信息
openssl x509 -in certificate.crt -text -noout
# 证书内容示例
# Certificate:
# Data:
# Version: 3 (0x2)
# Serial Number:
# 04:cd:3a:5b:f2:7c:8a:3e:9d:5e:8a:3c:1d:4e:8a:2b:3c
# Signature Algorithm: sha256WithRSAEncryption
# Issuer: C = US, O = Let's Encrypt, CN = R3
# Validity
# Not Before: Jan 15 00:00:00 2026 GMT
# Not After : Apr 15 00:00:00 2026 GMT
# Subject: CN = example.com
# Subject Public Key Info:
# Public Key Algorithm: rsaEncryption
# RSA Public-Key: (2048 bit)
1.3 证书链的概念
浏览器验证证书时,不是只看你服务器自己提供的这一张纸,而是要顺着藤蔓摸到根上,验证整个证书链:
- 端实体证书:你自己的服务器证书。
- 中间证书:由 CA 颁发给中间机构的证书,用于隔离根证书。
- 根证书:内置于浏览器或操作系统中的、绝对受信任的根 CA 证书。
# 查看证书链
openssl verify -CAfile chain.crt certificate.crt
# 查看证书链的完整路径
openssl s_client -connect example.com:443 -showcerts </dev/null 2>/dev/null | \
openssl x509 -noout -text | grep -A 2 "Certificate chain"
二、证书过期的风险
2.1 服务不可用风险
当证书过期那一刻,最直接的后果就是用户访问你的网站时,会迎面撞上一堵“此网站不安全”的警告墙。浏览器只会显示冷冰冰的安全警告页面,尽管用户可以强行点开“高级”按钮手动继续访问,但绝大多数普通用户的第一反应会是直接关掉页面,毫不犹豫地抛弃你。如果是移动端 App 的 API 证书过期,那更惨,所有功能直接报错、闪退,用户可能会以为你的公司倒闭了。
2.2 数据安全风险
不要以为只是访问不了那么简单。过期的证书就像一个形同虚设的门卫,无法为通信提供完整有效的加密保护。特别是在用户因为频繁看到警告而逐渐麻痹,开始习惯性地忽略安全提示的场景下,中间人攻击的风险会急剧增加。此外,如果你需要遵循某些安全合规要求,比如 PCI DSS,那过期的证书会让你直接掉出合规名单。
2.3 运维应急压力
更让人血压飙升的是,证书过期常常精准地发生在你最不希望的时间——比如某个安静的凌晨或悠闲的假期。届时,你将面对的不只是技术问题,还有连环夺命 Call、领导关切的目光、客户愤怒的投诉,以及在一片焦头烂额中,手动操作续期可能因为手抖而犯下的更多低级错误。
2.4 实际案例:证书过期导致的服务中断
某电商平台就曾经历过这样一次惨痛的教训:凌晨 3 点,客服突然被潮水般的投诉淹没,用户反馈无法下单。团队一顿手忙脚乱的排查后,发现根源竟是支付接口的 HTTPS 证书过期了,导致握手失败。整个服务中断持续了整整 2 个小时,事后估算的直接业务损失超过百万。复盘时发现,根本原因很简单:证书续期流程完全依赖某个开发人员手动处理,并且没有任何监控和告警。
三、证书管理的基本操作
3.1 证书的获取方式
获取证书通常有几种途径:
- 商业 CA:像 DigiCert、GlobalSign 这样的大厂,提供付费的高信任度证书。
- Let's Encrypt:一个免费、自动化的 CA 新星,主打 90 天有效期的证书,是个人和小微企业的首选。
- 私有 CA:自己动手,丰衣足食,适合企业内网环境。
3.2 Let's Encrypt 证书申请
Let's Encrypt 是目前最流行的免费证书提供商,通过 ACME 协议实现了自动化的证书申请和续期。
3.2.1 安装 certbot
# Ubuntu/Debian
sudo apt update
sudo apt install certbot python3-certbot-nginx
# CentOS/RHEL
sudo dnf install certbot python3-certbot-nginx
# 查看版本
certbot --version
# certbot 3.0.1
3.2.2 申请证书
# 为单个域名申请证书
sudo certbot certonly --nginx -d example.com
# 为多个域名申请证书
sudo certbot certonly --nginx -d example.com -d www.example.com
# 使用 webroot 模式申请(需要预先配置 webroot)
sudo certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com
# 使用 DNS 验证方式申请(适合无法通过 HTTP 验证的场景)
sudo certbot certonly --manual --preferred-challenges dns -d example.com
3.2.3 证书存放位置
# Let's Encrypt 证书存放位置
ls -la /etc/letsencrypt/live/example.com/
# 文件说明
# fullchain.pem - 完整证书链(服务器证书 + 中间证书)
# privkey.pem - 私钥
# cert.pem - 服务器证书
# chain.pem - 中间证书
3.3 证书的部署
3.3.1 Nginx 配置
server {
listen 443 ssl http2;
server_name example.com;
# 证书配置
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# TLS 版本配置(禁用旧版本)
ssl_protocols TLSv1.2 TLSv1.3;
# 加密套件配置
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# OCSP stapling 配置
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
}
# HTTP 重定向到 HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
3.3.2 Apache 配置
<VirtualHost *:443>
ServerName example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem
# TLS 配置
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
</VirtualHost>
3.4 证书的验证
# 检查证书信息
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -dates
# 检查证书链
openssl verify /etc/letsencrypt/live/example.com/fullchain.pem
# 在线检查证书
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
openssl x509 -noout -dates -issuer -subject
# 检查 TLS 版本和加密套件
echo | openssl s_client -connect example.com:443 -tls1_2 -servername example.com 2>/dev/null | \
openssl s_client -connect example.com:443 -tls1_3 2>/dev/null | grep "Protocol"
3.5 证书的续期
Let's Encrypt 的证书只有 90 天寿命,续期是永恒的话题。
# 手动续期所有证书
sudo certbot renew
# 手动续期特定证书
sudo certbot renew --cert-name example.com
# 测试续期流程(不实际执行)
sudo certbot renew --dry-run
# 强制续期(忽略过期时间)
sudo certbot renew --force-renewal
四、自动化证书管理
4.1 certbot 自动续期机制
certbot 在安装后就非常体贴地为你创建了定时任务。
# 查看定时任务
sudo systemctl list-timers | grep certbot
# 查看详细配置
sudo cat /etc/cron.d/certbot
# 0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && certbot -q renew
# 查看 systemd timer
sudo systemctl status certbot-renew.timer
4.2 Nginx 自动部署
certbot 的 nginx 插件可以一键搞定申请和部署。
# 使用 nginx 插件申请证书并自动配置
sudo certbot --nginx -d example.com -d www.example.com
# 插件会自动:
# 1. 验证域名
# 2. 申请证书
# 3. 修改 Nginx 配置文件
# 4. 配置自动续期
4.3 续期后自动重载服务
续期后最重要的是让 Web 服务器用上新证书,这靠 hook 脚本来实现。
# 查看 certbot 的 hook 配置
sudo cat /etc/letsencrypt/renewal/example.com.conf
# 配置示例
[renewalparams]
account = xxxxxxxx
authenticator = nginx
installer = nginx
server = https://acme-v02.api.letsencrypt.org/directory
post_hook = systemctl reload nginx
4.4 完整的自动化脚本
#!/bin/bash
# /opt/scripts/cert-auto-renew.sh
LOGFILE="/var/log/cert-renewal.log"
CERT_DIR="/etc/letsencrypt/live"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOGFILE
}
# 执行证书续期
log "Starting certificate renewal check..."
sudo certbot renew --quiet --deploy-hook "systemctl reload nginx" 2>&1 | tee -a $LOGFILE
# 检查续期结果
if [ $? -eq 0 ]; then
log "Certificate renewal completed successfully"
# 列出需要更新的证书
for cert in $(ls $CERT_DIR); do
expiry=$(sudo openssl x509 -in $CERT_DIR/$cert/cert.pem -noout -enddate 2>/dev/null | cut -d= -f2)
log "Certificate $cert expires: $expiry"
done
else
log "ERROR: Certificate renewal failed"
# 发送告警
# 这里接入告警通知
fi
五、证书监控方案
主动出击,比亡羊补牢要有效得多。
5.1 证书有效期监控
5.1.1 脚本监控
#!/bin/bash
# /opt/scripts/check-cert-expiry.sh
ALERT_DAYS=30
CERT_FILE="/etc/letsencrypt/live/example.com/cert.pem"
ALERT_EMAIL="ops@example.com"
# 获取证书过期时间
EXPIRY_DATE=$(sudo openssl x509 -in $CERT_FILE -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_UNTIL_EXPIRY=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "Certificate expires in $DAYS_UNTIL_EXPIRY days (on $EXPIRY_DATE)"
if [ $DAYS_UNTIL_EXPIRY -le $ALERT_DAYS ]; then
echo "WARNING: Certificate will expire soon!" | mail -s "Certificate Expiry Alert" $ALERT_EMAIL
exit 1
fi
5.1.2 OpenSSL 命令检查远程证书
#!/bin/bash
# 检查远程网站的证书过期时间
DOMAIN=$1
ALERT_DAYS=${2:-30}
# 获取证书过期时间
EXPIRY_DATE=$(echo | openssl s_client -servername $DOMAIN -connect $DOMAIN:443 2>/dev/null | \
openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -z "$EXPIRY_DATE" ]; then
echo "ERROR: Cannot get certificate for $DOMAIN"
exit 1
fi
# 计算剩余天数
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_UNTIL_EXPIRY=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "$DOMAIN: expires in $DAYS_UNTIL_EXPIRY days (on $EXPIRY_DATE)"
if [ $DAYS_UNTIL_EXPIRY -le $ALERT_DAYS ]; then
echo "ALERT: $DOMAIN certificate will expire in $DAYS_UNTIL_EXPIRY days"
exit 1
fi
使用示例:
# 检查多个域名
for domain in example.com api.example.com shop.example.com; do
/opt/scripts/check-cert-expiry.sh $domain 30
done
5.2 Prometheus 监控
利用业界标准的 Prometheus 和 blackbox_exporter 可以轻松实现证书监控。
配置 prometheus.yml:
# prometheus 配置
scrape_configs:
- job_name: 'ssl-certificate-exporter'
static_configs:
- targets:
- example.com:443
- api.example.com:443
metrics_path: /probe
params:
module: [https_02]
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: ssl-certificate-exporter:9119
用容器启动 blackbox_exporter:
docker run -d \
--name ssl_exporter \
-p 9119:9119 \
prom/blackbox_exporter:latest
告警规则:
# /etc/prometheus/rules/cert-alerts.yml
groups:
- name: certificate-expiry
rules:
- alert: CertificateExpiringSoon
expr: ssl_cert_expires_in_seconds < 86400 * 30
for: 1h
labels:
severity: warning
annotations:
summary: "Certificate {{ $labels.instance }} expiring in less than 30 days"
description: "Certificate expires on {{ $value | humanizeDuration }}"
- alert: CertificateExpired
expr: ssl_cert_expires_in_seconds < 0
for: 5m
labels:
severity: critical
annotations:
summary: "Certificate {{ $labels.instance }} has expired"
description: "Certificate expired on {{ $labels.instance }}"
5.3 Grafana 仪表板
你也可以在 Grafana 中创建一个可视化面板,让过期风险一目了然所需的关键指标有:
- 证书剩余有效期(天)
- 证书剩余有效期的百分比
- 证书过期的倒计时
- 按域名分组的证书列表
{
"panels": [
{
"title": "Certificate Expiry",
"type": "stat",
"targets": [
{
"expr": "ssl_cert_expires_in_seconds / 86400",
"legendFormat": "{{ instance }}"
}
],
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{ "value": 0, "color": "red" },
{ "value": 7, "color": "orange" },
{ "value": 30, "color": "green" }
]
}
}
}
}
]
}
5.4 云服务商监控
主流云服务商也都提供了证书监控服务,比如:
- AWS Certificate Manager:可设置过期前 30/45/60/90 天的通知。
- 阿里云 SSL 证书:支持证书到期提醒。
- 腾讯云 SSL 证书:提供到期自动续费功能。
六、证书管理工具
6.1 acme.sh
acme.sh 是一个用 Shell 编写的轻量级 Let's Encrypt 客户端,功能强大且零依赖。
# 安装 acme.sh
curl https://get.acme.sh | sh -s email=my@example.com
# 申请证书
.acme.sh/acme.sh --issue -d example.com -d www.example.com --nginx /etc/nginx/nginx.conf
# 安装证书
.acme.sh/acme.sh --install-cert -d example.com \
--key-file /etc/nginx/ssl/key.pem \
--fullchain-file /etc/nginx/ssl/fullchain.pem \
--reloadcmd "systemctl reload nginx"
6.2 Vault PKI
HashiCorp Vault 则提供了企业级的 PKI 解决方案,适合内部服务的证书管理。
# 启用 PKI secrets engine
vault secrets enable pki
# 配置 CA
vault write pki/root/generate/internal \
common_name="example.com Internal CA" \
ttl=87600h
# 创建角色
vault write pki/roles/example-dot-com \
allowed_domains="example.com" \
allow_subdomains=true \
max_ttl=72h
# 生成证书
vault read pki/issue/example-dot-com \
common_name="app.example.com"
6.3 cert-manager(Kubernetes)
对于运行在 Kubernetes 上的服务,cert-manager 是事实上的标准,它以云原生的方式管理证书。
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ops@example.com
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- http01:
ingress:
class: nginx
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-com-tls
namespace: production
spec:
secretName: example-com-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- example.com
- www.example.com
duration: 2160h # 90天
renewBefore: 360h # 提前15天续期
七、私有证书管理
7.1 建立私有 CA
对于完全在内网的环境,自建一个私有 CA 是最高效省事的办法。
# 创建私有 CA 私钥
openssl genrsa -aes256 -out ca.key 4096
# 创建 CA 证书
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
-out ca.crt \
-subj "/CN=Internal Root CA/O=Example Inc"
# 分发 CA 证书到服务器
sudo cp ca.crt /usr/local/share/ca-certificates/internal-ca.crt
sudo update-ca-certificates
7.2 签发内部证书
# 为内部服务签发证书
openssl genrsa -out internal.key 2048
# 创建证书签名请求
openssl req -new -key internal.key \
-out internal.csr \
-subj "/CN=internal.example.com/O=Example Inc"
# 签发证书
openssl x509 -req -in internal.csr \
-CA ca.crt -CAkey ca.key \
-CAcreateserial \
-out internal.crt \
-days 365 \
-sha256
# 验证证书
openssl verify -CAfile ca.crt internal.crt
八、常见问题处理
8.1 证书链不完整
# 问题:浏览器显示证书链无效
# 解决:确保使用 fullchain.pem 而不是 cert.pem
# Nginx 配置
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # 正确
ssl_certificate /etc/letsencrypt/live/example.com/cert.pem; # 错误
# 验证证书链
openssl s_client -connect example.com:443 -showcerts </dev/null 2>/dev/null | \
openssl x509 -noout -text | grep "Certificate chain"
8.2 私钥权限问题
# 问题:Nginx 无法读取私钥
# 解决:确保私钥权限为 600
ls -la /etc/letsencrypt/live/example.com/privkey.pem
# -rw------- 1 root root 1675 Jan 15 00:00 privkey.pem
# 如果权限不正确
sudo chmod 600 /etc/letsencrypt/live/example.com/privkey.pem
sudo chown root:root /etc/letsencrypt/live/example.com/privkey.pem
8.3 OCSP 响应器问题
# 问题:OCSP stapling 验证失败
# 解决:更新中间证书或禁用 OCSP stapling
# 查看中间证书
openssl x509 -in /etc/letsencrypt/live/example.com/chain.pem -text -noout | grep Issuer
# 临时禁用 OCSP stapling
ssl_stapling off;
ssl_stapling_verify off;
8.4 续期失败处理
# 查看续期失败原因
sudo certbot renew --dry-run 2>&1
# 常见失败原因及解决方案
# 1. Web 服务未运行
sudo systemctl start nginx
sudo certbot renew
# 2. 域名 DNS 解析问题
dig example.com
# 确保 DNS 解析正常后再续期
# 3. 80 端口被占用
sudo netstat -tlnp | grep :80
# 释放端口或使用 DNS 验证方式
# 4. Let's Encrypt 限流
# 等待限流解除,或使用 --staging 参数测试
sudo certbot renew --staging
九、最佳实践
9.1 证书管理规范
- 统一使用 Let's Encrypt 证书,除非有商业证书的强制要求。
- 证书有效期统一设置为 90 天,续期设置为到期前 30 天。
- 证书文件统一存放在
/etc/letsencrypt/live/{domain}/ 目录。
- 私钥文件权限设置为 600,所有者为 root。
9.2 监控告警规范
- 设置双重告警:证书到期前 30 天(提醒)和 7 天(严重)各一次。
- 告警内容应包含:域名、剩余天数、过期时间、处理建议。
- 告警渠道应多渠道送达:邮件、短信、即时通讯(如钉钉、飞书)。
- 定期测试告警通道是否畅通,别让告警成了摆设。
9.3 自动化规范
- 所有证书申请、部署、续期必须实现自动化。
- 自动续期后必须自动重载 Web 服务配置。
- 保留证书历史的审计日志,方便回溯。
- 定期执行灾备演练,验证证书恢复流程。
9.4 应急预案
- 准备手动续期的操作手册,别让线上紧急情况无章可循。
- 保留历史证书的备份,以便紧急回滚。
- 关键系统的证书应准备多套备用,以防万一。
- 建立 24 小时应急响应机制,就是大家俗称的“On-Call”。
总结
HTTPS 证书管理是运维工作中不可忽视的重要环节。证书过期的后果很严重,从用户无法访问到业务中断,再到数据安全风险,都可能造成重大损失。
理解 HTTPS 证书的工作原理是做好管理的基础:公钥密码学保证了通信安全,证书链验证确保了身份可信,证书有效期控制着信任范围。
Let's Encrypt 为免费自动化打开了大门,配合 certbot 或 acme.sh 这类工具,你已经可以把大部分人力解脱出来。
监控告警是防止证书过期的最后一道防线。我始终认为,主动监控远比被动响应更重要,至少设置 30 天和 7 天两档告警是一个普遍认可的好习惯。
日常运维中,不要等到火烧眉毛才想起管理流程。建立规范的证书管理流程,包括申请、部署、监控、应急预案,并定期演练和优化,才能真正做到高枕无忧。如果遇到疑难杂症,也欢迎来云栈社区和大家一起交流探讨,抱团取暖总比单打独斗强。