运维实践

ACME 协议详解:RFC 8555、三种验证方式与 ARI

作者: · 最后核实

ACME(Automatic Certificate Management Environment,自动证书管理环境)是让软件代替人去申请、验证、续期和吊销 TLS 证书的协议。它在 2019 年 3 月成为 RFC 8555:所有请求都用账户私钥签名,通过 http-01、dns-01 等“挑战”证明域名控制权,最后提交 CSR 拿到证书。今天主流免费 CA 和大部分商业 CA 都支持 ACME。

ACME 解决了什么问题

ACME 把买证书时的一连串手工步骤变成了一个定时任务就能驱动的 API。在 ACME 之前,申请证书意味着:生成 CSR、粘贴到网页表单、点验证邮件里的链接或手动上传验证文件,最后再手动部署。每张证书都要花时间,而且一旦有人忘了,证书就悄无声息地过期。

Let's Encrypt 为 2015 年上线设计了 ACME,IETF 在 2019 年完成标准化。CA/Browser Forum 的 Baseline Requirements 直接认可了 ACME 的验证方法:3.2.2.4.19 节对应 http-01,3.2.2.4.7 节(DNS 变更)对应 dns-01,3.2.2.4.20 节对应 TLS-ALPN。随着证书有效期在 2029 年降到 47 天,ACME 已经从“方便”变成“事实上必需”,详见 自动续期。

目录(directory)与各类资源

每个 ACME 服务器只公布一个目录 URL,其他地址都从这里发现。下面是截至 2026 年 11 月 Let's Encrypt 生产环境的目录:

{
  "keyChange": "https://acme-v02.api.letsencrypt.org/acme/key-change",
  "meta": {
    "caaIdentities": ["letsencrypt.org"],
    "profiles": {
      "classic": "https://letsencrypt.org/docs/profiles#classic",
      "shortlived": "https://letsencrypt.org/docs/profiles#shortlived",
      "tlsserver": "https://letsencrypt.org/docs/profiles#tlsserver"
    },
    "termsOfService": "https://letsencrypt.org/documents/LE-SA-v1.8-July-06-2026.pdf",
    "website": "https://letsencrypt.org"
  },
  "newAccount": "https://acme-v02.api.letsencrypt.org/acme/new-acct",
  "newNonce": "https://acme-v02.api.letsencrypt.org/acme/new-nonce",
  "newOrder": "https://acme-v02.api.letsencrypt.org/acme/new-order",
  "renewalInfo": "https://acme-v02.api.letsencrypt.org/acme/renewal-info",
  "revokeCert": "https://acme-v02.api.letsencrypt.org/acme/revoke-cert"
}
资源 作用
newNonce 在 Replay-Nonce 响应头里返回一个新的防重放 nonce
newAccount 创建或找回绑定某个公钥的账户
newOrder 为一个或多个标识符(域名/IP)发起证书订单
Authorization(授权) 每个标识符一个,里面挂着可选的挑战
Challenge(挑战) 证明控制权的具体方式:http-01、dns-01、tls-alpn-01
finalize 每个订单专属,用来提交 CSR
Certificate 每个订单专属,用来下载证书链
revokeCert 吊销证书
keyChange 更换账户私钥
renewalInfo RFC 9773 新增的 ARI 接口

meta.caaIdentities 就是你在 CAA 记录 里授权这家 CA 时要填的值。

每个请求都是签名的 JWS

ACME 靠签名而不是密码或 API Token 认证。每个 POST 请求体都是一个 JSON Web Signature,受保护头部里写明算法、nonce、正在调用的完整 URL,以及账户:

{
  "protected": base64url({
    "alg": "ES256",
    "kid": "https://acme-v02.api.letsencrypt.org/acme/acct/1234567890",
    "nonce": "918MM8jNCGhHSIKmADOgmJbHgyvZRtj4QF5fM6dr95rPfCqLw7A",
    "url": "https://acme-v02.api.letsencrypt.org/acme/new-order"
  }),
  "payload": base64url({"identifiers": [{"type": "dns", "value": "example.com"}]}),
  "signature": "..."
}

由此有三条规则。第一,newAccount 请求在 jwk 字段里带完整公钥,之后的请求都用 kid 指向账户 URL。第二,nonce 只能用一次,服务器返回 badNonce 错误时,客户端从响应里取新的 nonce 重试即可。第三,连“读”也要签名:ACME 用 POST-as-GET(payload 为空的 POST)来获取订单和证书,服务器因此始终知道是哪个账户在请求。

账户与外部账户绑定(EAB)

ACME 账户本质上就是在 CA 那里登记的一对密钥。newAccount 请求可以带联系邮箱,必须声明同意服务条款。Let's Encrypt 自 2025 年 6 月 4 日起不再发送到期提醒邮件,所以邮箱已经没那么重要。

商业 CA 通常要求 外部账户绑定(EAB),见 RFC 8555 第 7.3.4 节:CA 在控制台给你一个 Key ID 和一个 HMAC 密钥,客户端用它们证明新建的 ACME 账户属于某个付费客户。账户私钥与证书私钥是两回事,可以用 keyChange 单独轮换,不需要重新签发证书。

订单、授权与状态机

一个订单对应一张证书,状态按固定路线流转。客户端向 newOrder 提交想要的标识符,拿到每个标识符的授权,逐一完成挑战,再用 CSR 完成订单。

              newOrder
                 |
                 v
   +---------> pending --------------------+
   |             |  所有授权都变成 valid    |  任一授权失败或过期
   |             v                         v
   |           ready  --(finalize + CSR)--> invalid
   |             |                         ^
   |             v                         |
   |         processing ----(CA 出错)------+
   |             |
   |             v
   |           valid  --> 下载证书链
   |
 授权:pending -> valid | invalid;valid -> expired | deactivated | revoked
 挑战:pending -> processing -> valid | invalid

已经验证通过的授权,在一段时间内可以被同一账户的后续订单复用。按 SC-081v3,Baseline Requirements 规定这个复用期最长 200 天,2027 年 3 月 15 日起 100 天,2029 年 3 月 15 日起 10 天。Let's Encrypt 更严格:tlsserver 和 shortlived 配置下授权只能复用 7 小时。

三种挑战方式

三种挑战都基于同一个 key authorization 字符串,它把 CA 给出的随机 token 和你的账户公钥绑在一起:

keyAuthorization = token + "." + base64url( 账户公钥的 SHA-256 JWK 指纹 )
http-01 dns-01 tls-alpn-01
证明放在哪里 http://<域名>/.well-known/acme-challenge/<token> _acme-challenge.<域名> 的 TXT 记录 443 端口上临时出示的自签名证书
内容 key authorization 原文 base64url(SHA-256(keyAuthorization)) 关键扩展 acmeIdentifier(OID 1.3.6.1.5.5.7.1.31),内含 SHA-256(keyAuthorization)
端口 只能 80 DNS 443,ALPN acme-tls/1
通配符 不支持 支持 不支持
IP 地址 支持 不支持 支持
典型场景 单台 Web 服务器 通配符、内网主机、多台服务器 TLS 反向代理、托管平台

http-01 是多数客户端的默认方式。Let's Encrypt 最多跟随 10 次跳转,但只接受跳到 80 或 443 端口,跳到 HTTPS 时也不校验证书。负载均衡后面的每台服务器都必须能返回同一个文件。在国内,未备案域名的 80 端口经常被云厂商拦截,http-01 会直接超时。

dns-01 证明的是对整个 DNS 区的控制,是申请 通配符证书 的唯一途径,也适用于不对公网开放的主机。Let's Encrypt 按正常 DNS 规则解析 TXT 记录,所以你可以用 CNAME 把 _acme-challenge 委托给另一个更方便自动化的区。代价是 DNS API 密钥会进入签发流程,DNSPod、阿里云解析的 AccessKey 尽量用最小权限的子账号。

tls-alpn-01 由 RFC 8737 定义,取代了 Let's Encrypt 在 2019 年 3 月移除的、不够安全的 tls-sni-01。服务器必须识别这个特殊的 ALPN 协议,只在这类连接里出示验证证书,所以它更适合反向代理,而不是普通 Web 服务器。

Let's Encrypt 会从多个网络位置同时验证。这个做法后来通过 SC-067 投票成为行业规则:域名验证和 CAA 检查都要做多视角验证(MPIC),让局部 BGP 劫持更难骗过 CA。

完成订单、下载与吊销

所有授权都通过后订单变为 ready,客户端把 CSR 提交到 finalize。CSR 里的域名必须和订单完全一致。服务器把订单转为 processing,再变为 valid,客户端从证书 URL 下载 application/pem-certificate-chain 格式的证书链。如果 CA 提供备用链,会放在 Link: rel="alternate" 响应头里。

吊销请求发往 revokeCert,可以由下单账户签名,也可以由证书自己的私钥签名。后一种方式意味着:拿到泄露私钥的任何人都能吊销这张证书,即使没有账户。

ARI:由 CA 告诉你何时续期

ACME Renewal Information(RFC 9773)新增了 renewalInfo 接口,针对某张证书返回建议续期窗口。证书标识由“授权密钥标识符(AKI)的 base64url + 点 + 序列号的 base64url”拼成:

GET /acme/renewal-info/aYhba4dGQEHhs3uEe6CuLN4ByNQ.AIdlQyE

HTTP/1.1 200 OK
Retry-After: 21600
{
  "suggestedWindow": {
    "start": "2025-01-02T04:00:00Z",
    "end":   "2025-01-03T04:00:00Z"
  },
  "explanationURL": "https://acme.example.com/docs/ari"
}

客户端在窗口内随机选一个时间续期,并按 Retry-After 的间隔再次查询;下新订单时带上 replaces 字段,指明要替换的旧证书。CA 需要批量吊销时可以把窗口提前,支持 ARI 的客户端就会赶在吊销之前换好证书,而不是等出了故障再补救。Let's Encrypt 建议每天至少查询两次。

Profile:选择证书类型

Profile 让客户端在 CA 提供的几种证书变体之间选择。CA 在目录的 meta.profiles 里列出可选项,客户端在 newOrder 请求里加上 "profile": "tlsserver" 即可。这套机制来自 IETF ACME 工作组草案《ACME Profiles Extension》(draft-ietf-acme-profiles),Let's Encrypt 已经实现。

Let's Encrypt profile 有效期 授权复用 说明
classic(默认) 90 天;2027-02-10 起 64 天;2028-02-16 起 45 天 30 天 带 Common Name;RSA 密钥带 Key Encipherment
tlsserver 2026-05-13 起 45 天 7 小时 不带 Common Name 和 Subject Key ID,最多 25 个域名
shortlived 160 小时 7 小时 同 tlsserver,另外支持 IP 地址

Let's Encrypt Generation Y 层级签发的证书只含服务器认证 EKU,不能再用于 TLS 客户端认证,详见 mTLS。

ACME 客户端

实际使用中,ACME 请求由 Certbot、acme.sh、lego 这类客户端代发,或者由 Web 服务器自己完成。下表是常用的开源客户端和内置 ACME 的服务器,ssl.sh 与它们均无合作关系。

客户端 形式 说明
Certbot Python 命令行 Certbot 4.0 起支持 --preferred-profile;DNS 插件多
acme.sh Shell 脚本 内置 DNSPod、阿里云、腾讯云等大量 DNS API;无需 root
lego Go 命令行/库 支持大量 DNS 服务商
Caddy Web 服务器 默认自动 HTTPS
nginx ngx_http_acme_module Web 服务器模块 2025 年 8 月发布的官方模块
cert-manager Kubernetes 控制器 Issuer、Certificate 资源

注意 acme.sh 的默认 CA 并不是 Let's Encrypt。想用 Let's Encrypt,先执行 acme.sh --set-default-ca --server letsencrypt。宝塔面板的“Let's Encrypt”申请功能底层同样是 ACME。

常见错误

ACME 错误以 urn:ietf:params:acme:error: 开头的问题文档返回,常见类型都有对应的处理办法:

错误类型 常见原因
badNonce nonce 过期,客户端应自动重试
unauthorized 挑战内容不对,或响应的不是这台服务器
connection CA 连不上 80/443 端口,见 certbot 超时
dns NXDOMAIN、SERVFAIL 或 TXT 记录缺失
caa CAA 记录禁止这家 CA 签发,见 CAA 报错
rateLimited 订单或失败次数过多,见 频率限制
rejectedIdentifier CA 不为这个域名签发

调试时先用 CA 的测试(staging)目录。测试环境签发的证书不受信任,但频率限制宽松、报错一致,配置有问题会先在这里暴露,不会把生产环境的额度耗光。

常见问题

该选哪种验证方式?

服务器能在 80 端口响应目标域名时用 http-01,最容易自动化。要通配符证书、主机不对公网开放、或者多台服务器共用一个域名时用 dns-01。tls-alpn-01 主要给无法使用 80 端口的 TLS 反向代理和托管平台用。国内服务器 80 端口常因未备案被拦截,这种情况下 dns-01 更稳。

ACME 只能配合 Let's Encrypt 用吗?

不是。ACME 是 IETF 标准,Google Trust Services、ZeroSSL、Sectigo、DigiCert 等都提供 ACME 接口,商业 CA 一般要求外部账户绑定(EAB),把 ACME 账户和你的客户账户关联起来。step-ca、HashiCorp Vault 等私有 CA 也支持 ACME。

账户私钥和证书私钥有什么区别?

账户私钥用来给 ACME 请求签名、向 CA 表明身份,永远不会出现在证书里;证书私钥是 CSR 里那把、服务器做 TLS 用的密钥。两者分开,轮换其中一个不影响另一个;账户私钥可以通过 keyChange 更换。

客户端已经在剩余 1/3 寿命时续期,还需要 ARI 吗?

1/3 阈值是很好的兜底,但只有 ARI 能让你提前知道 CA 要批量吊销或者想错峰。Let's Encrypt 建议每天至少查两次 ARI,查不到时再按剩余 1/3 寿命续期。

资料来源

  1. RFC 8555:Automatic Certificate Management Environment (ACME)
  2. RFC 8737:ACME TLS-ALPN 验证扩展
  3. RFC 9773:ACME Renewal Information (ARI) Extension
  4. IETF 草案:ACME Profiles Extension
  5. Let's Encrypt:Challenge Types
  6. Let's Encrypt:Profiles
  7. CA/Browser Forum Ballot SC067v3:多视角验证