证书 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 记录,后面再也不用管了:
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,验证完删掉,整个过程不中断服务:
# 安装 sudo snap install certbot --classic # 一条命令搞定申请 sudo certbot certonly --nginx \ -d example.com \ -d www.example.com \ --email admin@example.com \ --agree-tos
装完看一眼证书状态:
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 就行:
server { listen 80; server_name example.com; location /.well-known/acme-challenge/ { root /var/www/certbot; } }
然后跑:
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 服务器的场景。
sudo systemctl stop nginx sudo certbot certonly --standalone \ -d example.com \ --email admin@example.com \ --agree-tos sudo systemctl start nginx
自动续期的正确姿势
证书拿到了,怎么自动续?Certbot 的 renew 命令会扫描所有本地证书,距离过期 30 天内的自动续。
# 先跑一遍 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 协议了,意味着付费证书也能走同样的自动化流程。
# 注册 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 一次搞定所有机器:
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,证书申请和续期全自动:
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 端口关了,续期就会失败。加一层监控兜底:
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 点被告警叫醒才后悔。