运维实践
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 寿命续期。