HTTPS 证书申请与 certbot 自动续期实践
证书选型与 ACME 流程
Let's Encrypt 免费证书有效期 90 天,适合绝大多数 Web 站点。签发走 ACME v2 协议,客户端推荐 certbot,版本不低于 2.0。域名验证有两种方式:HTTP-01 需要在 /.well-known/acme-challenge/ 下放置校验文件,必须保证 80 端口可被公网访问;DNS-01 通过 TXT 记录校验,适合泛域名 *.example.com,因为 HTTP-01 无法签发通配符证书。
# 安装 sudo apt install certbot python3-certbot-nginx # 申请(Nginx 插件自动改配置) sudo certbot --nginx -d example.com -d www.example.com # 仅签发,不改配置 sudo certbot certonly --webroot -w /var/www/html -d example.com
证书落在 /etc/letsencrypt/live/example.com/,其中 fullchain.pem 与 privkey.pem 是部署时需要的两个文件。ACME 账号与证书的私钥目录权限保持 700,避免泄露。
自动续期与 cron 配置
certbot 安装包自带 systemd timer,执行 systemctl list-timers | grep certbot 可以看到每日两次的触发。手动方案用 cron:
0 3 * * * /usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"
renew 只对剩余有效期小于 30 天的证书执行续签,所以每天跑是安全的。--deploy-hook 只在真正续签成功后触发,免得每天无意义地 reload Nginx。验证流程是否正常,用 certbot renew --dry-run,它会走完整 ACME 流程但不实际替换证书。
坑一:webroot 校验 404 导致续期失败
站点配置了 HTTPS 强制跳转,80 端口的请求被 301 到 443,ACME 服务器拿到跳转而不去跟随,校验直接失败,报错 Invalid response from http://example.com/.well-known/acme-challenge/xxx: 404。改写跳转规则,放行校验路径:
location ^~ /.well-known/acme-challenge/ {
root /var/www/html;
default_type "text/plain";
}
location / {
return 301 https://$host$request_uri;
}改完 nginx -t && systemctl reload nginx,再跑一次 dry-run 确认。另一种情况是前端 CDN 回源时把该路径也缓存了旧 301,需要在 CDN 侧对 /.well-known/ 配置不缓存。
坑二:续期后服务未重载,线上仍是旧证书
证书文件已经更新,但 Nginx worker 进程仍持有旧证书的内存句柄,浏览器看到的有效期没变。检查方式:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \ | openssl x509 -noout -dates
输出的 notAfter 应与 /etc/letsencrypt/live/example.com/fullchain.pem 上的日期一致。不一致说明没 reload。
Nginx 的证书是 master 进程启动时加载的,reload 会重新读取文件,SIGHUP 同样可以。用 deploy-hook 保证续签后自动重载;如果用 Docker 挂载证书,需要在容器外执行 docker exec nginx nginx -s reload,容器内 cron 无法访问宿主机路径,这是常见的静默失败原因。
小结
证书本身的有效期策略决定了必须自动化:90 天有效期加 30 天续签窗口,人工维护必然出问题。关键点集中在三处:80 端口校验路径不能被跳转或缓存拦截;--deploy-hook 中完成服务重载;用 renew --dry-run 和 openssl s_client 分别验证续签流程与线上实际证书。把这两条检查放进监控告警,基本可以避免到期才发现的问题。
- 点赞
- 收藏
- 关注作者
评论(0)