跳到正文
Let's Encrypt 教我的不是免费证书,而是续期必须可验证
Let's Encrypt 教我的不是免费证书,而是续期必须可验证

Let's Encrypt 教我的不是免费证书,而是续期必须可验证

从 HTTP-01、DNS-01、Certbot timer 和 dry-run 讲清自动续期,也说明迁移到 Cloudflare Tunnel 后,访客证书、Tunnel 通道和本机服务分别由谁保护。

速读#

最早给 Nginx 站点加 HTTPS 时,我选择了 Let’s Encrypt 和 Certbot。真正留下来的经验不是“免费证书最好”,而是任何有期限的运行凭据都不能依赖记忆:签发路径要能重复,续期任务要真实存在,配置变化后要重新演练,外部还要监控证书到期时间。certbot renew --dry-run 很有用,却不是永久保证,它只证明当时使用 staging CA 的一次模拟流程通过。

当前站点已经迁到 Cloudflare Tunnel,访客到边缘的公开证书由 Cloudflare 管理,边缘通过加密 Tunnel 到 cloudflared,本机 Nginx 只监听回环 HTTP,因此这条公开链路不再需要 Certbot。这个变化没有推翻旧经验,只是把证书责任换了位置:先画清每段连接由谁终止 TLS,再决定是否需要 Let’s Encrypt、Origin CA、内部证书或根本不在本机启用 TLS。

Let’s Encrypt 是一个选择,不是个人站唯一答案#

自签证书不受公共浏览器信任,适合自己管理信任根的内网;公共 CA 证书适合浏览器直接访问的站点;托管平台、Cloudflare、Caddy 和部分反向代理又可以把申请与续期一起接管。商业证书也不必然手工续期,Let’s Encrypt 的主要优势是开放、免费、短周期并支持 ACME 自动化,而不是“其他方案都不能用”。

当时我的环境是 Debian、Nginx 和一个直接对公网开放 80/443 的域名,Certbot 的 Nginx 插件是维护成本最低的路径:

Terminal window
sudo apt update
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d app.example.com

这个命令会读取并修改 Nginx 配置,适合先备份配置、查看 diff,再执行 nginx -t。如果不希望工具自动改 server block,可以使用 certbot certonly --nginx 只获取证书,自己管理证书路径、跳转和 reload;自由度增加的同时,续期后重新加载服务也要自己验证。

验证方式决定网络要求#

Let’s Encrypt 在签发前要证明申请者控制域名,不同 ACME challenge 的网络边界不同:

方式验证怎样发生适合场景主要边界
HTTP-01CA 访问 http://域名/.well-known/acme-challenge/...普通单域名 Web 站点必须从公网经 80 端口到达挑战响应,不能签泛域名
DNS-01在权威 DNS 中创建 _acme-challenge TXT泛域名、无公网 Web 入站DNS API 凭据权限和传播时间要管理
TLS-ALPN-01CA 在 443 上进行专用 TLS 验证客户端与入口支持该方式时443 必须由能响应 challenge 的组件控制

“Certbot 必须开放 80”只对使用 HTTP-01 的这条路径成立。请求可以经过 CDN 或反向代理,Let’s Encrypt 也允许有限的 HTTP/HTTPS 重定向,但公网最终必须在标准端口得到正确 challenge;WAF、Access、全站重写和错误的 location 规则都可能拦截它。泛域名必须使用 DNS-01,完全不希望开放 Web 入站时,DNS challenge 通常也更合适。

DNS API token 同样是密钥,不应把全账号 Global API Key 塞进脚本。只授予所需 Zone 的 DNS 编辑权限,把凭据放在 root 可读的仓库外文件中,并确认命令和日志不会输出完整值。自动签证书如果以长期高权限 DNS token 为代价,风险可能比手工操作更大,所以权限范围与轮换步骤必须一起设计。

90 天有效期为什么改变了我的习惯#

Let’s Encrypt 的 90 天证书缩短了误签或私钥泄露后的自然有效窗口,也迫使部署者尽早自动化。短周期不是说“证书泄露后等 90 天就行”:发现私钥或账号异常仍应立即吊销、替换密钥并调查影响;它只是限制最坏情况下凭据长期不变的时间。

真正重要的是续期任务不依赖人的日历。不同发行版和 Certbot 版本可能使用 systemd timer 或 cron,执行频率和实际续期窗口也会受证书生命周期、CA 的 ACME Renewal Information 等机制影响,因此我不再死记“每天两次、到期前 30 天”。应该直接检查当前机器:

Terminal window
systemctl status certbot.timer
systemctl list-timers certbot.timer
sudo certbot certificates
sudo journalctl -u certbot.service --since '30 days ago'

这些命令分别回答 timer 是否启用、下次何时运行、本机管理哪些证书,以及最近续期服务是否失败。只看到 package 已安装,不等于调度器正在工作;只看到 timer active,也不等于 challenge、DNS 凭据和 Nginx reload 仍然有效。

Dry-run 是演练,不是到期保证#

Terminal window
sudo certbot renew --dry-run

Dry-run 通常使用 Let’s Encrypt staging 环境完成模拟续期,能提前暴露 challenge 路由、防火墙、插件、权限和 reload hook 等问题。它尤其适合在修改 Nginx、DNS、Cloudflare 规则、端口或 Certbot 插件后立即执行,而不是一年想起一次。配置漂移可能发生在任何一次变更后,证书过期前才检查会把几个月的隐患集中到最后一天。

它仍有边界:staging 与生产 CA 的速率、账号和服务状态不同,今天通过也不能保证两个月后网络和 DNS 不会变化。还需要一个独立的外部到期告警,直接从用户视角读取证书,而不是只相信本机 Certbot 数据:

Terminal window
echo | openssl s_client \
-connect app.example.com:443 \
-servername app.example.com 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates

监控应在剩余时间足够人工处理时告警,并覆盖实际对外主机名。内部证书续上了、负载均衡器仍在提供旧证书,或者某个遗漏子域已经过期,都不会被单纯的 certbot certificates 发现。

Cloudflare 之后,先区分三段 TLS#

使用 Cloudflare 代理时,至少要区分访客到 Cloudflare、Cloudflare 到源站,以及源站内部代理到应用三段。普通代理回源到公网 HTTPS 时,推荐 Full (strict) 并使用公共 CA 或 Cloudflare Origin CA 证书;Origin CA 证书通常只受 Cloudflare 信任,访客若绕过 Cloudflare 直连源站,浏览器不会把它当普通公共证书,这正是它的使用边界。

Cloudflare Tunnel 又不同。当前站点由 cloudflared 主动建立加密通道,Tunnel 的 service 指向本机回环 Nginx HTTP;这一小段不经过不受信网络,本机也没有公开 80/443,所以没有必要为了形式再给回环套一层 Certbot。若 cloudflared 连接的是局域网另一台机器、跨主机容器网络或 HTTPS origin,则应重新评估那一段的窃听与身份验证风险,并按 Tunnel origin 参数正确验证证书,不能照搬“内部 HTTP 永远安全”。

迁移后还要删除已经不再使用的公网监听、旧证书续期任务和防火墙规则,否则表面上走 Tunnel,源站 443 仍在继续接受直连。可以通过 ss -lntup、外部端口测试和 Certbot 证书列表确认旧链路真的退出,而不是两套入口长期并存。

我现在保留的检查顺序#

需要公开 HTTPS 时,我先画连接边界,再按顺序确认:谁为哪个主机名提供证书,使用哪种 ACME challenge,续期由哪个 timer 或平台负责,凭据放在哪里,续期后谁 reload,外部到期告警是否覆盖,最后怎样在不影响生产的情况下演练。任何一项说不清,都不能只凭浏览器今天显示小锁就宣布完成。

Let’s Encrypt 最重要的影响,是让我接受“自动化也要被监控和复测”。证书会到期,DNS 和代理规则会变化,工具也会升级;可靠性不来自设置过一次自动续期,而来自这条路径在变化后仍然能够被外部证据证明。

版权许可

CC BY-NC-SA 4.0 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

相关文章

s1oopX

登录 s1oopX