凌晨3点到期的证书:SSL/TLS自动化实用指南
SSL/TLS证书到期是最令人尴尬且最可预防的生产环境故障之一。领英、Spotify、Microsoft Teams以及无数小型组织都经历过与证书相关的故障——这并非因为证书管理在技术上有多困难,而是因为手动流程最终会失败。本指南涵盖了完整的证书生命周期:ACME和Let’s Encrypt的内部工作原理、如何自动化颁发和续订、如何跨多台服务器管理证书,以及如何设置监控,在用户发现问题之前就捕捉到问题。
ACME工作原理:Let’s Encrypt背后的协议
Let’s Encrypt使用ACME协议(自动证书管理环境,RFC 8555)颁发免费、公开信任的TLS证书。理解ACME的机制可以帮助您调试故障并为您的基础设施选择合适的挑战类型。
ACME流程有四个步骤:
- 账户注册:您的ACME客户端生成密钥对并向ACME服务器(Let’s Encrypt的Boulder CA)注册。公钥标识您的账户。
- 订单创建:您为一个或多个域名请求证书。ACME服务器创建一个订单并返回一组授权挑战——您必须完成这些挑战以证明对每个域名的控制权。
- 挑战完成:您完成提供的挑战类型之一(HTTP-01、DNS-01或TLS-ALPN-01)。ACME服务器验证您的完成情况并将授权标记为有效。
- 证书颁发:您提交包含您的域名和公钥的证书签名请求(CSR)。CA对其进行签名并返回证书链。
挑战类型及使用场景
HTTP-01:ACME服务器期望在http://yourdomain.com/.well-known/acme-challenge/{token}找到特定文件。您的服务器必须在端口80上可公开访问。这是对Web服务器最简单的挑战,但对于内部服务、通配符证书以及位于严格防火墙后面的服务器会失败。
DNS-01:您在_acme-challenge.yourdomain.com创建一个具有特定值的TXT记录。ACME服务器通过DNS查找验证它。此挑战适用于通配符证书和内部服务。权衡之处在于它需要访问您DNS提供商的API——这是一个重要的安全考虑,因为DNS凭证具有很大的影响范围。
TLS-ALPN-01:使用较少,此挑战完全通过端口443上的TLS工作。当您无法修改DNS记录且无法提供HTTP流量,但可以接受TLS连接时很有用。
Certbot:参考实现
Certbot 是电子前哨基金会(EFF)的 ACME 客户端,也是文档最全面的选择。对于独立的 Nginx 或 Apache 服务器,它可以处理完整的证书生命周期。
# 在 Ubuntu/Debian 上安装带 Nginx 插件的 certbot
apt install certbot python3-certbot-nginx
# 获取并安装证书(Nginx 插件会自动修改 nginx.conf)
certbot --nginx -d example.com -d www.example.com
# 试运行测试续订而不实际续订
certbot renew --dry-run
# Certbot 安装了 systemd 定时器用于自动续订
systemctl status certbot.timer
# certbot.timer 每天运行两次,续订将在 30 天内过期的证书
# 使用预/后钩手动续订(例如,用于重新加载服务)
certbot renew
--pre-hook "systemctl stop nginx"
--post-hook "systemctl start nginx"
--deploy-hook "systemctl reload nginx"
对于 Certbot 的 DNS-01 挑战,您需要一个与您的提供商匹配的 DNS 插件。Certbot 为大多数主要 DNS 提供商维护插件:
# 通过 Cloudflare 的 DNS-01 获取通配符证书
pip install certbot-dns-cloudflare
# 创建凭证文件(请仔细限制权限)
cat > /etc/letsencrypt/cloudflare.ini << 'EOF'
dns_cloudflare_api_token = your_api_token_here
EOF
chmod 600 /etc/letsencrypt/cloudflare.ini
# 获取通配符证书
certbot certonly
--dns-cloudflare
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini
-d "*.example.com"
-d example.com
--agree-tos
--email admin@example.com
Caddy:无需配置的自动 HTTPS
Caddy 采用了不同的方法:HTTPS 默认是自动的。您的 Caddyfile 中的每个域名都会从 Let’s Encrypt(或 ZeroSSL)获取证书,无需任何明确的证书配置。Caddy 自动处理颁发、存储、续订和重新加载。
# Caddyfile — 所有这些网站的 HTTPS 都是自动的
example.com {
reverse_proxy localhost:8080
}
api.example.com {
reverse_proxy localhost:3000
# 通过 Caddy 插件进行速率限制
rate_limit {
zone dynamic {
key {remote_host}
events 100
window 1m
}
}
}
# 使用自签名证书的内部服务(用于非公共域名)
internal.example.com {
tls internal
reverse_proxy localhost:9090
}
Caddy 默认将证书存储在 /var/lib/caddy/.local/share/caddy 中,并在证书距离过期 30 天内时自动续订。对于多服务器部署,Caddy 通过 Redis 或共享文件系统支持分布式证书存储,防止每个服务器独立请求同一域名的证书。
cert-manager:Kubernetes 中的证书自动化
cert-manager 是 Kubernetes 中 TLS 证书管理的实际标准。它引入了 Issuer/ClusterIssuer 资源来表示证书颁发机构,以及 Certificate 资源来请求特定证书。
# 通过 Helm 安装 cert-manager
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager
--namespace cert-manager
--create-namespace
--set crds.enabled=true
# 使用 Let's Encrypt 生产环境和 DNS-01 挑战(Cloudflare)的 ClusterIssuer
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
# 仅将此求解器应用于 example.com 及其子域名
selector:
dnsZones:
- "example.com"
---
# 请求通配符证书
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-example-com
namespace: production
spec:
secretName: wildcard-example-com-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- "*.example.com"
- "example.com"
# 在到期前30天续订
renewBefore: 720h
cert-manager 还与 Ingress 和 Gateway API 资源集成。添加注解后,cert-manager 会处理其余工作:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
namespace: production
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- api.example.com
secretName: api-example-com-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
证书监控:在用户之前发现过期问题
自动续费处理理想情况。监控则处理其他一切:续费失败、自动化范围外手动添加的证书、您无法控制的第三方服务上的证书。
# Shell脚本:检查域名列表的证书过期时间
#!/bin/bash
DOMAINS=(
"example.com"
"api.example.com"
"admin.example.com"
)
WARNING_DAYS=30
CRITICAL_DAYS=7
for domain in "${DOMAINS[@]}"; do
expiry=$(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" ]; then
echo "CRITICAL: 无法连接到 $domain"
continue
fi
expiry_epoch=$(date -d "$expiry" +%s 2>/dev/null ||
date -jf "%b %d %T %Y %Z" "$expiry" +%s)
now_epoch=$(date +%s)
days_remaining=$(( (expiry_epoch - now_epoch) / 86400 ))
if [ "$days_remaining" -lt "$CRITICAL_DAYS" ]; then
echo "CRITICAL: $domain 将在 $days_remaining 天后过期 ($expiry)"
elif [ "$days_remaining" -lt "$WARNING_DAYS" ]; then
echo "WARNING: $domain 将在 $days_remaining 天后过期 ($expiry)"
else
echo "OK: $domain 将在 $days_remaining 天后过期"
fi
done
对于生产环境监控,请使用专用工具而非基于cron的脚本。Checkly和UptimeRobot都提供带有Slack/PagerDuty集成的证书过期监控。Prometheus配合blackbox_exporter可以将证书过期作为指标进行监控:
# prometheus/blackbox.yml — TLS探测配置
modules:
https_cert_check:
prober: http
timeout: 10s
http:
valid_status_codes: [200]
tls_config:
insecure_skip_verify: false
preferred_ip_protocol: ip4
# Grafana告警规则:当证书在14天内过期时触发
# 指标: probe_ssl_earliest_cert_expiry - time()
# 条件: < 14 * 24 * 60 * 60 (秒)
多服务器证书分发
当您在负载均衡器后运行多个Web服务器时,需要一种分发证书的策略。常见有三种方法:
在负载均衡器处终止TLS:最简洁的方法。AWS ACM、Cloudflare或专用负载均衡器处理证书。您的后端服务器接收纯HTTP流量。证书在一个地方管理。缺点是负载均衡器与后端之间的流量未加密——对于VPC内部流量可以接受,但对于对合规性敏感的环境存在问题。
共享文件系统挂载:让Certbot在一个节点上运行,将证书存储在共享的NFS/EFS挂载点,配置所有Web服务器从该路径读取。简单但会在NFS挂载点创建单点故障。
cert-manager 与 Kubernetes Secrets 复制: 如果您的服务器运行在 Kubernetes 中,cert-manager 会将证书写入 Secrets,而 external-secrets-operator 可以在命名空间或集群之间复制它们。
运营检查清单
- 清点贵组织使用的每张证书,包括第三方服务、内部服务和客户端证书上的证书
- 设置监控,在到期前 30 天、14 天和 7 天发出警报 — 三个独立的警报阈值,严重程度递增
- 在生产环境依赖自动化之前,在暂存环境中测试续订 — 运行
certbot renew --dry-run或 cert-manager 的测试颁发者 - 将 ACME 账户私钥存储在密钥管理器中(Vault、AWS Secrets Manager),而不是文件系统上
- 为每个自动化流程记录手动续订程序 — 自动化可能会失败,有人需要在凌晨 2 点知道该做什么
- 使用证书透明度日志监控(crt.sh 或 Facebook 的 CT 监控器)来检测为您的域名颁发的未授权证书
关键要点
- ACME 的 DNS-01 挑战是通配符证书和无法通过端口 80 访问的服务所必需的。使用具有 API 作用域的 DNS 提供商令牌,而不是您的根账户凭据。
- Caddy 完全自动化 HTTPS,使其成为新部署的理想选择,在这些部署中,简单性比定制需求更重要。
- cert-manager 是 Kubernetes 证书管理的标准。与 Ingress 注释集成以实现最低摩擦的工作流程。
- 监控必须涵盖您自动化系统之外的证书。三年前在遗忘的子域上手动安装的证书不会自行续订。
- 始终要有记录的手动续订程序。自动化减少了手动干预的频率,而不是理解如何操作的需求。
