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

> 逐步拆解 ACME（RFC 8555）：账户、JWS 签名请求、订单状态机、http-01/dns-01/tls-alpn-01 验证、ARI 续期窗口与证书 profile。

Source: https://ssl.sh/zh/learn/acme-protocol/
Last updated: 2026-11-10

ACME（Automatic Certificate Management Environment，自动证书管理环境）是让软件代替人去申请、验证、续期和吊销 TLS 证书的协议。它在 2019 年 3 月成为 [RFC 8555](https://www.rfc-editor.org/rfc/rfc8555)：所有请求都用账户私钥签名，通过 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](https://ssl.sh/zh/glossary/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 已经从“方便”变成“事实上必需”，详见 [自动续期](https://ssl.sh/zh/learn/automate-certificate-renewal/)。

## 目录（directory）与各类资源

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

```json
{
  "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 记录](https://ssl.sh/zh/learn/caa-records/) 里授权这家 CA 时要填的值。

## 每个请求都是签名的 JWS

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

```text
{
  "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 完成订单。

```text
              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 和你的账户公钥绑在一起：

```text
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 区的控制，是申请 [通配符证书](https://ssl.sh/zh/glossary/wildcard-certificate/) 的唯一途径，也适用于不对公网开放的主机。Let's Encrypt 按正常 DNS 规则解析 TXT 记录，所以你可以用 CNAME 把 `_acme-challenge` 委托给另一个更方便自动化的区。代价是 DNS API 密钥会进入签发流程，DNSPod、阿里云解析的 AccessKey 尽量用最小权限的子账号。

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

Let's Encrypt 会从多个网络位置同时验证。这个做法后来通过 SC-067 投票成为行业规则：域名验证和 CAA 检查都要做**多视角验证**（[MPIC](https://ssl.sh/zh/glossary/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](https://www.rfc-editor.org/rfc/rfc9773)）新增了 `renewalInfo` 接口，针对某张证书返回建议续期窗口。证书标识由“授权密钥标识符（AKI）的 base64url + 点 + 序列号的 base64url”拼成：

```text
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](https://ssl.sh/zh/learn/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 超时](https://ssl.sh/zh/errors/certbot-timeout-during-connect/) |
| `dns` | NXDOMAIN、SERVFAIL 或 TXT 记录缺失 |
| `caa` | CAA 记录禁止这家 CA 签发，见 [CAA 报错](https://ssl.sh/zh/errors/caa-record-prevents-issuance/) |
| `rateLimited` | 订单或失败次数过多，见 [频率限制](https://ssl.sh/zh/errors/lets-encrypt-rate-limit/) |
| `rejectedIdentifier` | CA 不为这个域名签发 |

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