# Heroku ACM 自动证书管理：自定义域名 HTTPS 配置

> Heroku Automated Certificate Management（ACM）给自定义域名自动签证书：herokudns.com 目标地址、根域名记录、CAA 放行 Let's Encrypt、ACM 状态和失败原因。

Source: https://ssl.sh/zh/guides/heroku-automated-certificate-management/
Last updated: 2026-11-10

Heroku 的 Automated Certificate Management（ACM，自动证书管理）会为应用的自定义域名申请并续期 Let's Encrypt 证书，所有应用免费使用。做法是：逐个添加域名，把 DNS 指向 Heroku 给出的 `herokudns.com` 目标地址，ACM 就会签发证书，一般 45 到 60 分钟。证书在到期前一个月自动续期。如果改为上传自己的证书，这个应用的 ACM 会被关闭。

步骤和命令以 Heroku Dev Center 文档为准（截至 2026 年 11 月）。应用名 `example-app` 和域名 `example.com` 是示例。

## 速查表

| 问题 | Heroku 的做法 |
|---|---|
| 自动证书的 CA | Let's Encrypt（通过 ACM） |
| 费用 | 所有应用免费包含 |
| 子域名 `www.example.com` | `CNAME` 指向它自己的 `<随机名>.herokudns.com` |
| 根域名 `example.com` | `ALIAS`/`ANAME`/拉平的 `CNAME` 指向目标地址；不支持 A 记录 |
| CAA 要放行 | `letsencrypt.org` |
| ACM 通配符 | 通用运行时（Common Runtime）文档未提及；Private Space 规则见下文 |
| 上传自有证书 | 支持，`heroku certs:add`（会关闭 ACM） |
| 续期 | 自动，到期前一个月 |
| TLS 版本 | 只支持 TLS 1.2 和 TLS 1.3 |

## 国内访问说明

Heroku 的运行区域在境外，从中国大陆访问的速度和稳定性没有保证，这和证书配置无关。面向国内用户的业务、需要 ICP 备案的网站，建议放在阿里云、腾讯云等国内云上，证书参考 [阿里云 SSL 证书](https://ssl.sh/zh/guides/aliyun-ssl-certificate/) 或 [腾讯云 SSL 证书](https://ssl.sh/zh/guides/tencent-cloud-ssl-certificate/)。

## 第一步：添加域名

根域名和 `www` 要分别添加，Heroku 规定根域名必须在子域名之外单独加：

```bash
heroku domains:add www.example.com -a example-app
heroku domains:add example.com -a example-app
heroku domains -a example-app
```

每个域名都有自己的 DNS 目标地址，例如 `whispering-willow-5678.herokudns.com`。添加域名不收费，但 Heroku 账户必须先完成验证。

## 第二步：把 DNS 指向目标地址

每个域名都指向 Heroku 为它分配的 `herokudns.com` 目标地址，根域名不能用 A 记录：

| 名字 | 类型 | 记录值 |
|---|---|---|
| `www.example.com` | `CNAME` | `whispering-willow-5678.herokudns.com` |
| `example.com` | `ALIAS`、`ANAME` 或拉平的 `CNAME` | `hidden-sierra-7936.herokudns.com` |

Heroku 明确说根域名只用 A 记录是不够的，因为它的 IP 地址是动态变化的。支持 ALIAS 或 ANAME 的服务商有 DNSimple、DNS Made Easy、easyDNS、Namecheap、DreamHost 等，Cloudflare 会在根域名上自动拉平 CNAME。如果你的 DNS 服务商在根域名上只能加 A 记录，就把 DNS 换到支持这些记录类型的服务商。

老应用的记录可能还指向 `example-app.herokuapp.com` 或 `*.herokussl.com`，ACM 无法验证这种配置，要改成 `herokudns.com` 的目标地址。另外不要加 `AAAA` 记录，ACM 不支持 IPv6。

## 第三步：检查 CAA 记录

域名设了 CAA 的话，加一条放行 Let's Encrypt：

```dns
example.com.  3600  IN  CAA  0 issue "letsencrypt.org"
```

否则 ACM 会失败，原因显示为 **CAA record prevents issuance, CAA record does not match issuer**。如果 DNS 服务商不支持 CAA，还可能出现 DNS provider doesn't support CAA。可以用 [CAA 检测工具](https://ssl.sh/zh/tools/caa-checker/) 确认。

## 第四步：启用 ACM 并查看状态

`heroku certs:auto:enable` 开启 ACM，`heroku certs:auto` 查看每个域名的签发状态：

```bash
heroku certs:auto:enable -a example-app
heroku certs:auto -a example-app
```

生成证书一般要 45 到 60 分钟，状态含义如下：

| 状态 | 含义 |
|---|---|
| **Cert Issued** | 证书已签发。 |
| **In Progress** | 正在验证域名的 DNS。 |
| **DNS Verified** | DNS 已验证，正在生成证书，还没结束。 |
| **Waiting** | 正在排队等待处理。 |
| **Failing** | 无法验证 DNS，会继续重试最多一小时。 |
| **Failed** | 无法验证 DNS，已停止重试。 |

## 通配符和自有证书

通配符域名可以加到应用上，DNS 用 `*` 的 CNAME 指向它的目标地址。但 Heroku 的 ACM 文档并没有说 ACM 会在通用运行时里签发通配符证书。关于 Private Space，文档说要自行上传通配符证书，Cedar 代的 Private Space 应用用 ACM 时不能使用通配符域名，而新一代的 Private Space 应用可以。

要用 EV 证书、别家 CA 的证书或通配符证书，就上传：

```bash
heroku certs:add server.crt server.key -a example-app
heroku certs -a example-app
```

`server.crt` 里要包含中间证书。上传后 ACM 自动关闭，以后想回到 ACM，运行 `heroku certs:auto:enable`。Heroku 不允许导出私钥，自己要留好备份。Private Space 应用想让通配符证书自动续期，Heroku 推荐 SSL FastTrack、ExpeditedSSL 这类插件。

## 从手动证书切换到 ACM

ACM 和 Heroku SSL（SNI）用的是同一套 DNS 配置，Private Space 应用启用 ACM 也不需要改 DNS。验证 DNS 期间，应用会继续使用你原来上传的证书，自定义域名一直可以访问。Heroku 文档针对 Private Space 应用给出的切换顺序是：

1. 用 `heroku certs` 找到手动上传的证书名称；
2. 运行 `heroku certs:auto:enable` 启用 ACM；
3. 用 `watch heroku certs:auto` 盯着，等新证书签发完成，这一步没完成就动手删旧证书，应用可能会停机；
4. 最后用 `heroku certs:remove --name 证书名称` 删除手动上传的证书。

顺序不要颠倒：先删旧证书再等 ACM，中间这段时间域名上就没有可用的证书了。

## 验证

证书签发后，自定义域名返回 Let's Encrypt 签发的证书。用 [SSL 证书检查](https://ssl.sh/zh/tools/ssl-checker/) 检查，或运行：

```bash
curl ssl.sh/example.com
```

查看证书内容：

```bash
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -issuer -subject -ext subjectAltName -dates
```

在 macOS 上做这项检查时不要用 `curl -k`：Heroku 提到很多版本的 macOS 上带 `-k` 的 curl 不发送 SNI。

## 自动续期

ACM 在证书到期前一个月续期，添加或删除自定义域名时也会自动生成新证书。如果 ACM 一直签不出证书，尝试三天后 Heroku 会发通知。上传的证书 Heroku 不会续期，要用 `heroku certs:update` 自己替换。Let's Encrypt 从 2025 年 6 月 4 日起不再发到期提醒邮件，默认有效期 2027 年 2 月 10 日起变成 64 天，关键域名建议加到 [ssl.sh 监控](https://ssl.sh/zh/monitor/)（开发中）。

## 常见失败原因

不要为了加快速度反复关闭再开启 ACM，可能触发 Let's Encrypt 频率限制。先排除原因，再运行：

```bash
heroku certs:auto:refresh -a example-app
```

Heroku 文档列出的失败原因：

- **CN exceeding 64 character limit**：域名超过 64 个字符。
- **Incorrect DNS settings**：记录配错、域名 DNSSEC 验证失败，或 Private Space 应用没有运行中的 web dyno。
- **Unable to resolve DNS for domain**：记录还没生效，ACM 用 Google Public DNS（8.8.8.8）检查。
- **CDN not returning HTTP challenge**：域名指向了 CloudFront 等 CDN，验证文件返回 404。
- **DNS redirect not forwarding path**：DNS 服务商的转发功能丢掉了验证文件的路径。
- **Incorrect CNAME target**：CNAME 指向了 `<app-name>-<random-identifier>.herokuapp.com`，而不是 DNS 目标地址。
- **Rate limited**：7 天内为这些域名申请证书次数太多，等限制解除后再 refresh，见 [Let's Encrypt 频率限制](https://ssl.sh/zh/errors/lets-encrypt-rate-limit/)。
- **DNS provider timed out**：DNS 服务商超时，refresh 重试。
- **Strict TLS in CDN not supported**：前面套了 Cloudflare 但配置不对；Heroku 不建议 ACM 和 Cloudflare 一起用，可以关掉 ACM。
- **DNS provider doesn't support CAA、CAA record prevents issuance, CAA record does not match issuer**：见第三步和 [CAA 记录阻止签发](https://ssl.sh/zh/errors/caa-record-prevents-issuance/)。
- **Domain record does not exist**：Let's Encrypt 查到 NXDOMAIN，检查域名拼写和 CNAME。
- **Domain considered unsafe**：域名被 Google 安全浏览标记为不安全。
- **Let's Encrypt domain blocklist**：域名在 Let's Encrypt 的屏蔽名单上，去 Let's Encrypt 社区论坛求助。
