证书申请与自动续期
证书是 HTTPS 的入场券,也是最容易被忘在脑后的运维项:申请时一切顺利,一年后某天早上浏览器开始报「证书已过期」,你才发现没人记得续期。本章把申请、续期、监控三件事一次做完——目标不是「拿到一张证书」,而是「以后再也不用手动管证书」。
要解决的问题
- Let's Encrypt 的免费证书和商业证书差在哪,该怎么选?
certbot --nginx、--webroot、--standalone三种方式怎么选?- 通配符证书为什么必须用 DNS 校验?
- 自动续期到底有没有在跑,怎么提前发现它坏了?
- 证书到期怎么监控、怎么告警?
- 私钥要不要备份、权限怎么给?
- 国内服务器上 HTTPS 和 ICP 备案是什么关系?
ACME 与 Let's Encrypt
一句话原理:ACME 是一套自动签发协议,客户端(certbot)向 CA 证明「我控制这个域名」,CA 就签一张 90 天有效期的证书。域名控制权通过三种挑战方式证明:
| 挑战方式 | 做法 | 是否需要停服务 | 能签通配符 |
|---|---|---|---|
| HTTP-01 | CA 访问 http://域名/.well-known/acme-challenge/xxx | 否 | 不能 |
| DNS-01 | 在 DNS 里加一条 _acme-challenge TXT 记录 | 否 | 可以 |
| TLS-ALPN-01 | 在 443 端口用特殊 ALPN 应答 | 需占用 443 | 不能 |
三种申请方式
# 安装(Debian/Ubuntu;CentOS 系用 dnf install certbot python3-certbot-nginx)
sudo apt -y update && sudo apt -y install certbot python3-certbot-nginx
# 方式一:--nginx —— 自动改 Nginx 配置并 reload,最省事
sudo certbot --nginx -d example.com -d www.example.com
# 方式二:--webroot —— 只往站点目录写校验文件,不动任何 Nginx 配置(推荐)
sudo certbot certonly --webroot -w /srv/www/site \
-d example.com -d www.example.com
# 方式三:--standalone —— certbot 自己监听 80 端口,必须先把 Nginx 停掉
sudo systemctl stop nginx
sudo certbot certonly --standalone -d example.com -d www.example.com
sudo systemctl start nginx
# 通配符:只能用 DNS-01,交互式手动加 TXT 记录
sudo certbot certonly --manual --preferred-challenges dns \
-d '*.example.com' -d example.com
三种方式的取舍:
| 方式 | 是否改 Nginx 配置 | 优缺点 | 建议 |
|---|---|---|---|
--nginx | 会改 | 最省事,但配置会被工具改动,日后排查多一层不确定性 | 小站、快速起步 |
--webroot | 不改 | 配置完全自己掌控,续期也走同一路径 | 生产推荐 |
--standalone | 不改 | 需要停 Nginx,续期时有停机风险 | 仅在无 Web 服务或首次初始化时用 |
--standalone 最大的坑不是首次申请,而是自动续期:某天凌晨 timer 触发,它要占用 80 端口却撞上正在运行的 Nginx,续期静默失败。所以生产环境优先 --webroot,或者给 standalone 配 --pre-hook "systemctl stop nginx" --post-hook "systemctl start nginx"。
通配符证书如果嫌手动加 TXT 麻烦,可以用 DNS 插件全自动:
sudo apt -y install python3-certbot-dns-cloudflare
# 凭据文件权限必须 600,内容形如 dns_cloudflare_api_token = xxxx
sudo certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials /root/.secrets/cf.ini \
-d '*.example.com' -d example.com
云厂商插件(certbot-dns-aliyun、certbot-dns-tencentcloud、certbot-dns-cloudflare)都是这个套路,配好后通配符证书也能全自动续期。
自动续期
# 1) 确认定时任务存在(certbot 包会自带 systemd timer,一天跑两次)
systemctl list-timers | grep -i certbot
# 2) 空跑一次,验证续期链路是否真的通(这一步比什么都重要)
sudo certbot renew --dry-run
# 3) 手动执行续期 + 续期成功后 reload Nginx
sudo certbot renew --deploy-hook "systemctl reload nginx"
# 4) 查看本机所有证书与到期时间
sudo certbot certificates
Let's Encrypt 证书有效期 90 天,certbot 会在剩余 30 天以内时自动续期,所以「一天跑两次、每次只处理快过期的」是安全的。hook 也可以用脚本方式固化:在 /etc/letsencrypt/renewal-hooks/deploy/ 下放一个可执行脚本(内容就是一行 systemctl reload nginx)并 chmod +x,这样无论谁触发续期都会自动 reload。注意 hook 只在「真的续期成功」时执行,空跑不会触发。
到期监控与告警
自动续期也可能因为 DNS 解析变了、80 端口被封、插件 token 过期而失败,所以必须有独立于 certbot 的到期检查:
# 直接用 openssl 查线上证书剩余时间,不依赖本机状态
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -enddate
# 纳入每日巡检:剩余不足 14 天就告警(-checkend 单位是秒)
if ! echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -checkend 1209600; then
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"msgtype":"text","text":{"content":"证书 14 天内到期"}}' "$WEBHOOK_URL"
fi
商业证书 vs 免费证书
| 维度 | Let's Encrypt(免费) | 商业 DV 证书 | 商业 OV/EV 证书 |
|---|---|---|---|
| 有效期 | 90 天,需自动续期 | 1 年 | 1 年 |
| 信任度/加密强度 | 与商业证书完全相同 | 相同 | 相同 |
| 浏览器表现 | 完全一致 | 一致 | EV 不再显示绿色地址栏,差异很小 |
| 额外价值 | 无 | 保险赔付、技术支持、更长有效期 | 组织信息验证,常用于政企合规 |
| 适合谁 | 绝大多数个人站与中小企业 | 需要赔付承诺与工单支持 | 有明确合规/招标要求 |
技术上加密度没有区别,免费证书的短板只有「90 天要自动续期」和「没有赔付」。绝大多数站点用 Let's Encrypt 就是最优解;真正需要商业证书的场景通常是「甲方要求」「需要保险赔付」或「无法开放 80/DNS 自动化的内网环境」。
私钥权限与备份
# 私钥只有 root 可读;Nginx 由 root 启动的 master 进程读取,无需给 www-data 读权限
sudo ls -l /etc/letsencrypt/live/example.com/
sudo chmod 600 /etc/letsencrypt/live/example.com/privkey.pem
# 备份证书目录(含 ACME 账号密钥,恢复后可直接续期)
sudo tar czf /backup/letsencrypt-$(date +%F).tar.gz -C /etc letsencrypt
二进制目录 /etc/letsencrypt/live/ 里是软链,实际文件在 archive/,备份要么整个 /etc/letsencrypt 一起备份,要么不备份——只要自动化续期还在跑,重签比恢复更简单。
国内服务器与 ICP 备案
备案与证书是两件事,但缺了备案 HTTPS 也白配:国内(大陆)节点上用域名对外提供网站服务,必须先完成 ICP 备案,否则运营商会拦截 80/443 的域名访问,用户和搜索引擎都打不开,证书配得再标准也没用。CDN、对象存储、负载均衡绑定自有域名同样要求备案。备案的是「域名 + 接入商」,换服务商要办接入变更。香港/海外节点不需要备案,但访问速度和稳定性要另作权衡。
常见坑
| 坑 | 现象 | 做法 |
|---|---|---|
从没跑过 renew --dry-run | 到期那天才发现续期早就坏了 | 每季度空跑一次 |
用 --standalone 又没停 Nginx | 续期静默失败,日志里 80 端口被占用 | 改用 --webroot,或配 pre/post hook |
| 续期成功但没 reload | 证书文件换了,Nginx 还在用旧证书 | 配 --deploy-hook 或 renewal-hooks 脚本 |
| 只监控本机文件时间 | 拿到的是备份或未生效的证书 | 用 openssl s_client 查线上证书 |
域名解析没配 www | 签 -d www.example.com 时校验失败 | 先把解析与 Nginx server_name 补齐 |
| 备案没下来就上 443 | 国内节点域名访问被重置 | 先完成备案或用海外节点过渡 |
| 私钥权限 644 | 私钥可被普通用户读取 | chmod 600,属主 root |
小结:证书申请优先用 certbot certonly --webroot,配置归自己、续期链路也最稳;通配符只能走 DNS-01,配好云厂商插件后同样可以全自动;自动续期的三个动作是「确认 timer 在跑、renew --dry-run 验证通、--deploy-hook 续期后 reload」;再叠加一个用 openssl s_client -checkend 做的独立到期巡检,证书这件事就彻底不用操心了;最后记住国内服务器上「先备案,再谈 HTTPS」,备案是前提而不是可选项。