4、ACME协议与Let's Encrypt:从原理到自动续期

说到证书轮换,ACME 协议和 Let's Encrypt 是绕不开的话题。我个人觉得,这俩东西的出现,彻底改变了证书管理的游戏规则。以前申请个证书,填表、发邮件、等审核,折腾好几天。现在呢?几分钟搞定,还能自动续期。

说白了,ACME 就是一套让客户端和 CA(证书颁发机构)自动对话的协议。Let's Encrypt 是第一个大规模落地这套协议的 CA。你想想看,没有 ACME,我们哪来的免费证书自动续期?

核心认知:ACME 协议的核心价值在于「自动化」。它把证书申请、验证、签发、续期这整套流程,变成了机器与机器之间的标准对话。你只需要配好客户端,剩下的交给定时任务。

4.1 ACME 协议原理:到底在聊什么?

ACME 全称是 Automatic Certificate Management Environment。嗯,名字挺长,但逻辑其实很简单。它解决的核心问题就一个:如何证明你拥有某个域名。

我记得第一次看 ACME 的 RFC 时,觉得它设计得很巧妙。它把验证过程分成了几个清晰的步骤:

  1. 注册账号:客户端生成一对公私钥,在 CA 那边注册一个账户。
  2. 发起订单:客户端告诉 CA,我要为某个域名申请证书。
  3. 完成挑战:CA 给你出题,证明你确实控制这个域名。
  4. 签发证书:验证通过,CA 签发证书给你。
  5. 续期:证书快过期时,重复 2-4 步。

这里面最关键的是第三步——挑战。Let's Encrypt 支持两种主流挑战方式:

挑战类型 验证方式 适用场景 我的建议
HTTP-01 在 Web 服务器根目录放一个特定文件 有公网 HTTP 服务的场景 最常用,配置简单
DNS-01 在 DNS 解析记录里加一个 TXT 记录 通配符证书、无公网 IP 的场景 支持通配符,但需要 DNS API

我的经验:如果你只需要单域名证书,优先用 HTTP-01。它不需要操作 DNS,速度快。但如果你要申请 *.example.com 这种通配符证书,那就只能用 DNS-01。我曾经在一个内网服务上折腾了半天 HTTP-01,最后发现根本行不通——因为 CA 访问不到我的内网服务器。

为什么会这样?因为 CA 验证时,是从公网发起请求的。你的服务器如果在内网,CA 根本找不到你。这时候 DNS-01 就派上用场了——它只验证 DNS 记录,不关心你的服务器在哪。

4.2 Certbot 客户端:最常用的 ACME 客户端

Certbot 是 Let's Encrypt 官方推荐的客户端。我用过不少 ACME 客户端,比如 acme.sh、lego,但 Certbot 的生态最完善,文档也最全。

安装 Certbot 很简单。以 Ubuntu 为例:

# 安装 Certbot 和 Nginx 插件
sudo apt update
sudo apt install certbot python3-certbot-nginx

# 或者用 Snap(官方推荐方式)
sudo snap install certbot --classic
sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflare  # 如果需要 DNS 插件

我个人习惯用 Snap 版本,因为它会自动更新,省得我手动维护。嗯,这里要注意:如果你用 apt 安装,版本可能比较旧,建议用 Snap 或直接从 GitHub 下载。

4.2.1 申请证书:一行命令搞定

申请证书真的就是一行命令的事。假设你用的是 Nginx:

# 自动检测 Nginx 配置并申请证书
sudo certbot --nginx -d example.com -d www.example.com

这条命令会做几件事:

  • 自动修改 Nginx 配置,添加 SSL 相关指令
  • 完成 HTTP-01 挑战
  • 下载证书并保存到 /etc/letsencrypt/live/example.com/
  • 重载 Nginx 使配置生效

如果你不想让 Certbot 自动修改 Nginx 配置,可以用 certonly 模式:

# 只申请证书,不修改 Web 服务器配置
sudo certbot certonly --nginx -d example.com

证书申请成功后,你会看到类似这样的输出:

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/example.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/example.com/privkey.pem
 - Your certificate will expire on 2025-06-15.

避坑指南:我曾经遇到过一个问题——申请证书时提示「域名验证失败」。排查了半天,发现是 Nginx 的防火墙没放行 80 端口。CA 来验证时,根本访问不到我放的那个文件。记住:HTTP-01 挑战需要 80 端口可访问,DNS-01 挑战需要 DNS API 权限。这两个前提条件一定要先确认好。

4.2.2 证书文件结构

申请成功后,Certbot 会在 /etc/letsencrypt/live/你的域名/ 下生成四个文件:

文件 内容 用途
cert.pem 服务器证书(不含中间证书) 一般不用,除非你手动拼接
chain.pem 中间证书链 与 cert.pem 拼接使用
fullchain.pem cert.pem + chain.pem Nginx/Apache 配置中用的证书文件
privkey.pem 私钥 必须保密,权限设为 600

你配置 Web 服务器时,通常只需要两个文件:fullchain.pem 和 privkey.pem。Nginx 配置示例:

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # 其他配置...
}

4.3 自动续期配置:一劳永逸

Let's Encrypt 的证书有效期是 90 天。嗯,你没看错,只有 90 天。为什么这么短?说白了就是为了推动自动化。你想想看,如果证书有效期是一年,很多人可能就忘了续期。90 天的话,你必须用自动化工具来管理。

Certbot 自带续期功能。你只需要设置一个定时任务:

# 测试续期是否正常工作
sudo certbot renew --dry-run

# 如果测试通过,设置 crontab 定时任务
sudo crontab -e

# 添加以下行(每天凌晨 3 点检查续期)
0 3 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx"

这里有几个关键点:

  • --quiet:静默模式,只有出错时才输出日志
  • --post-hook:续期成功后执行的命令,通常是重载 Web 服务器
  • Certbot 会自动判断哪些证书需要续期(距离过期不足 30 天)

我的习惯:我不会只依赖 crontab。我会再加一个 systemd timer,这样日志管理更方便。而且我会在续期脚本里加一个健康检查——续期后自动访问一下 HTTPS 页面,确认证书生效。如果访问失败,发告警通知。这样即使续期出问题,我也能第一时间知道。

3.3.1 续期原理:为什么能自动续期?

自动续期的核心在于 ACME 协议的「无状态」设计。CA 不保存你的证书状态,每次续期都是重新走一遍申请流程。但 Certbot 会保存你的账户密钥和之前的挑战记录,所以续期时不需要你重新配置。

具体流程是这样的:

  1. Certbot 检查本地证书,发现距离过期不足 30 天
  2. 用保存的账户密钥向 CA 发起续期请求
  3. CA 重新验证域名所有权(再次完成挑战)
  4. 签发新证书,覆盖旧证书文件
  5. 执行 post-hook,重载 Web 服务器

整个过程完全自动化,你不需要任何人工干预。

4.4 核心流程可视化

下面这张图展示了 ACME 协议从申请到续期的完整流程。我把它画出来,方便你理解整个生命周期。

ACME 协议证书生命周期流程 Certbot 客户端 Let's Encrypt CA Web 服务器 DNS 服务器 ① 注册账户(交换公钥) ② 发起证书订单 ③ 放置验证文件 ④ CA 验证文件(HTTP-01) ⑤ 签发证书 ⑥ 配置 Web 服务器 定时任务:每 30 天自动续期 重复步骤 ②-⑥ 💡 关键说明: • HTTP-01 挑战需要 80 端口可访问;DNS-01 挑战需要 DNS API 权限 • 续期时 Certbot 会自动判断哪些证书需要更新(距离过期不足 30 天)

4.5 常见问题与避坑

最后,分享几个我实际踩过的坑:

  • 续期失败不告警:默认的 crontab 配置是静默模式,续期失败不会发邮件。我建议加上告警机制,比如续期失败后发 Slack 通知或邮件。
  • 证书文件权限:privkey.pem 必须只有 root 可读。我曾经遇到过权限设置不对,导致 Nginx 启动失败。
  • 多域名证书:如果你有多个域名,建议每个域名单独申请证书,而不是用 SAN 证书。这样某个域名出问题时,不影响其他域名。
  • DNS 插件兼容性:如果你用 DNS-01 挑战,一定要确认你的 DNS 服务商有对应的 Certbot 插件。Cloudflare、AWS Route53、阿里云都有插件,但一些小的 DNS 服务商可能没有。

重要提醒:千万不要手动修改 /etc/letsencrypt/live/ 下的证书文件。Certbot 续期时会自动覆盖这些文件。如果你手动改了,续期后会被覆盖掉。如果你需要自定义证书路径,用 --deploy-hook 把证书复制到目标位置。