证书 47 天就过期,还在手动续?聊聊我在 AWS 上的自动化方案
上周帮朋友排查一个线上故障,折腾两小时最后发现------证书过期了。就这么简单又离谱的原因。客户打不开页面,浏览器直接甩一个 NET::ERR_CERT_DATE_INVALID,朋友还以为是 DNS 出了问题,查了半天。
更离谱的是,这种事以后会越来越频繁。CA/B Forum 去年通过了 Ballot SC-081,证书有效期会一路缩:398 天 → 200 天(2026.3)→ 100 天(2027.3)→ 47 天(2029.3)。到 2029 年,一个半月不续就挂。你管 5 台机器可能还能记住,管 20 台呢?靠人肉置巡?不现实。
今天分享一下我在亮马逊云科技上搞证书自动化的实践,踩过的坑一并说了。
先选路线:你的证书该谁管
在亚马逊云科技上搞 TLS,第一步是想清楚你属于哪种架构:
| 架构 | 方案 | 操心程度 |
|---|---|---|
| ALB/CloudFront/API Gateway | ACM 托管 | 完全不操心 |
| EC2 上跑 Nginx/Apache | Certbot + ACME | 需要配一次 |
| EKS Ingress | cert-manager | K8s 声明式 |
如果你的流量经过 ALB 或 CloudFront,直接用 AWS Certificate Manager(ACM),证书免费、自动续期、零配置。申请的时候加个 DNS 验证的 CNAME 记录,后面再也不用管了:
bash
aws acm request-certificate \
--domain-name "*.example.com" \
--validation-method DNS \
--region us-east-1
但现实是很多团队的服务直接跑在 EC2 上,TLS 终止在 Nginx。ACM 的证书没法导出私钥,装不到 Nginx 里。这种情况就得靠 ACME 协议 + Certbot 了。
Certbot 实战:三种模式怎么选
Nginx 插件模式(日常推荐)
Certbot 的 Nginx 插件会自动在你的 server block 里插入验证 location,验证完删掉,整个过程不中断服务:
bash
# 安装
sudo snap install certbot --classic
# 一条命令搞定申请
sudo certbot certonly --nginx \
-d example.com \
-d www.example.com \
--email admin@example.com \
--agree-tos
装完看一眼证书状态:
bash
sudo certbot certificates
# Certificate Name: example.com
# Domains: example.com www.example.com
# Expiry Date: 2026-09-15 (VALID: 89 days)
# Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem
# Private Key Path: /etc/letsencrypt/live/example.com/privkey.pem
Webroot 模式(不碠 Nginx 配置)
有些同事的 Nginx 配置比较复杂(比如用了大量 include 和自定义 map),不想让 Certbot 自动改配置文件。这时候用 Webroot 模式,自己加一段 location 就行:
nginx
server {
listen 80;
server_name example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
}
然后跑:
bash
sudo certbot certonly --webroot \
-w /var/www/certbot \
-d example.com \
-d www.example.com \
--email admin@example.com \
--agree-tos
Standalone 模式(临时用用)
Certbot 自己起一个 Web 服务器来验证,但它得占 80 端口,意味着你得先停掉 Nginx。生产环境别用这个做续期,适合新机器初次部署还没装 Web 服务器的场景。
bash
sudo systemctl stop nginx
sudo certbot certonly --standalone \
-d example.com \
--email admin@example.com \
--agree-tos
sudo systemctl start nginx
自动续期的正确姿势
证书拿到了,怎么自动续?Certbot 的 renew 命令会扫描所有本地证书,距离过期 30 天内的自动续。
bash
# 先跑一遍 dry-run,确认流程没问题
sudo certbot renew --dry-run
# 写 cron,每天凌晨 2:30 跑一次
echo "30 2 * * * root certbot renew --quiet --deploy-hook 'systemctl reload nginx'" \
| sudo tee /etc/cron.d/certbot-renew
踩坑重点 :--deploy-hook 和 --post-hook 的区别。
--deploy-hook:只在续期成功时执行--post-hook:不管有没有续期都执行
我之前用的 --post-hook,结果每天凌晨 cron 触发都会 reload Nginx,日志里全是无意义的 reload 记录。多张证书的时候更烦,一次 renew 跑完会 reload 好几次。改成 --deploy-hook 后世界清净了。
DigiCert ACME:企业级 OV/EV 也能自动化
Let's Encrypt 发的是 DV(域名验证)证书,免费好用。但有些企业内部合规要求 OV 或 EV 证书,显示公司名。好消息是 DigiCert 现在也支持 ACME 协议了,意味着付费证书也能走同样的自动化流程。
bash
# 注册 DigiCert ACME 账户(EAB 凭据从 DigiCert 管理后台获取)
sudo certbot register \
--server https://acme.digicert.com/v2/acme/directory \
--eab-kid "YOUR_KEY_ID" \
--eab-hmac-key "YOUR_HMAC_KEY" \
--agree-tos --email admin@example.com
# 申请证书
sudo certbot certonly --nginx \
--server https://acme.digicert.com/v2/acme/directory \
-d example.com
配好后续期流程跟 Let's Encrypt 完全一样,同一条 cron 通吃,不用额外改任何东西。
多台机器怎么管
单机 Certbot 配一次就好,但如果你有十几台 EC2 呢?一台台 SSH 过去配置不现实。三种思路:
思路一:流量收拢到 ALB。TLS 终止在负载均衡器,后端 EC2 只跑 HTTP。新服务强烈推荐这么干。
思路二:SSM 批量推送。用 AWS Systems Manager Run Command 一次搞定所有机器:
bash
aws ssm send-command \
--targets "Key=tag:Role,Values=webserver" \
--document-name "AWS-RunShellScript" \
--parameters 'commands=["snap install certbot --classic","certbot certonly --nginx -d $(hostname -f) --agree-tos -m admin@example.com -n"]'
思路三:EKS 用 cert-manager。K8s 环境下声明一个 ClusterIssuer,证书申请和续期全自动:
yaml
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-key
solvers:
- http01:
ingress:
class: nginx
监控兜底:别光靠自动化
自动化做好了不代表可以完全放手。万一哪天 Let's Encrypt 服务端抽风、DNS 解析出了问题、安全组把 80 端口关了,续期就会失败。加一层监控兜底:
python
import ssl, socket, datetime, boto3
def check_cert(domain):
ctx = ssl.create_default_context()
with ctx.wrap_socket(socket.socket(), server_hostname=domain) as s:
s.settimeout(5)
s.connect((domain, 443))
cert = s.getpeercert()
expiry = datetime.datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z')
return (expiry - datetime.datetime.utcnow()).days
def lambda_handler(event, context):
for domain in ['example.com', 'api.example.com']:
days = check_cert(domain)
if days < 14:
boto3.client('sns').publish(
TopicArn='arn:aws:sns:us-east-1:123456789012:cert-alerts',
Message=f'{domain} 证书还剩 {days} 天,请检查续期是否正常'
)
用 EventBridge 每天触发一次这个 Lambda 就行。14 天阈值给了你两周的缓冲来人工干预。
我的建议
- 能上 ALB + ACM 的优先上,省心到不需要解释
- EC2 场景 Certbot + Nginx 插件 + deploy-hook,一次配好长期不管
- 企业证书走 DigiCert ACME,同一套自动化流程
- 不管哪种方案,都加上监控,14 天阈值告警
- 配完立刻跑
--dry-run,别等到真正续期那天才发现配错了
证书有效期缩短已经是定局,趁现在还有 200 天的缓冲期起紧把自动化做了。别等凌晨 2 点被告警叫醒才后悔。