你在 Vercel、Cloudflare Pages、Netlify 或 Firebase 上加了自定义域名,dashboard 一直显示 “Provisioning SSL certificate”、“Issuing certificate” 或 “Pending validation”,几小时(有时一两天)都不变绿。访问 https://yourdomain.com 浏览器报 NET::ERR_CERT_AUTHORITY_INVALID,或者干脆连不上。
TL;DR(九成情况这一步就解决): 先跑 dig +short yourdomain.com @1.1.1.1,看返回值是不是和宿主 dashboard 让你填的完全一致。不一致就说明 DNS 填错了或还没传播,证书永远签不出来。DNS 没问题的话,接着跑 dig CAA yourdomain.com +short,一条老的 CAA 记录如果不包含你宿主的 CA,会悄无声息地把签发拦死。改好记录后回 dashboard 点 “Refresh” / “Retry”。多数宿主背后用的是 Let’s Encrypt 或 Google Trust Services,签证书本身只要几秒,所以卡住几乎一定是验证环节出问题,而不是 CA 慢。
本文按它们实际出错的顺序排查:先 DNS,再 CAA,最后是宿主特有的 HTTP-01 / proxy 问题。
你属于哪一类?
先跑这三条命令,结果会告诉你直接跳到哪一节。
| 命令 | 看到这个结果 | 跳到 |
|---|---|---|
dig +short yourdomain.com @1.1.1.1 | 空,或者 IP/CNAME 和 dashboard 不一致 | 原因 1(DNS) |
dig CAA yourdomain.com +short | 类似 0 issue "digicert.com" 且没列你的宿主 | 原因 2(CAA) |
curl -sI http://yourdomain.com/.well-known/acme-challenge/test | HTTP/.. 301 跳到 https:// | 原因 3(强制 HTTPS) |
如果这三条都正常,那多半是限速或 DNSSEC 问题(原因 4、5)。
常见原因
按命中率从高到低排。
1. DNS 还没传播,或者根本指错了
最常见。在域名服务商加了 A / CNAME 之后,旧的 TTL 还没过期,或者类型/值填错了:根域名(apex)只能用 A 不能用 CNAME,结果写了 CNAME;或者 A 指向了过时的 IP。宿主无法验证域名归属,CA 根本就不会被请求去签证书。
宿主 dashboard 会显示具体的期望值。这里有个 2026 年 6 月需要注意的变化:Vercel 现在会给每个项目分配专属的 DNS 目标(比如从 Anycast 地址池里给一个 A 记录 IP,或者一个像 xyz.vercel-dns-016.com 这样的 CNAME),而不再是统一的一个值。老的 76.76.21.21 和 cname.vercel-dns.com 仍然能解析、仍然有效,但请用你项目 Settings → Domains 页面显示的那个值,一个字符不差地照抄。
如何判断:
dig +short yourdomain.com
dig +short www.yourdomain.com
# 把每一条都和宿主 dashboard 里的确切值比对
2. CAA 记录把宿主的 CA 拦了
域名根上有 CAA 记录,限定了谁能签证书(比如只允许 digicert.com),但你的宿主用的是 letsencrypt.org 或 pki.goog(Google Trust Services)。CA 一读 CAA,发现自己不在白名单里,直接拒绝签发,宿主就一直停在 Pending。多数 dashboard 不会给出有用的报错,所以这一类最浪费时间。
典型触发场景:你从企业 DNS 平台或上一家宿主迁过来,老的 CAA 记录跟着一起带过来了。
如何判断:
dig CAA yourdomain.com +short
# 输出示例:0 issue "digicert.com"
# 如果白名单里没有 letsencrypt.org / pki.goog,这就是问题所在
3. HTTP-01 验证被强制 HTTPS / WAF 拦住
Let’s Encrypt 默认走 HTTP-01 验证:在 http://yourdomain.com/.well-known/acme-challenge/TOKEN 放一个验证文件,然后用纯 HTTP 来取。如果你用 Cloudflare 挡在前面,并开了 “Always Use HTTPS”(或加密模式设成 “Full (strict)”),这个纯 HTTP 请求会被 301 跳到 HTTPS,但 HTTPS 还没好,验证就 fail 了。Cloudflare 的 “Automatic HTTPS Rewrites” 以及对 .well-known/ 路径的激进缓存也会造成同样的失败。
WAF / 防火墙规则拦 .well-known/ 路径也是同类问题。
如何判断:
curl -sI http://yourdomain.com/.well-known/acme-challenge/test
# 出现 HTTP 301 -> https,说明重定向把验证给杀了
4. 限速或老证书冲突
域名之前在别处签过证书(包括通配符 *.yourdomain.com),或者多个项目同时请求完全相同的主机名集合,请求互相打架。Vercel 等宿主有时会报 Failed to issue certificate: too many certificates already issued。
截至 2026 年 6 月,这里会撞上的 Let’s Encrypt 限制:
- 同一组完全相同的主机名,7 天内最多 5 张重复证书(反复删了又加域名时最容易撞到的就是这个)。
- 同一个注册域名 7 天内最多 50 张证书(更宽的总量上限,单个小站不太容易撞到)。
续期不计入这些限制。另外注意 Let’s Encrypt 正在缩短默认证书有效期:45 天的 profile 从 2026 年年中起可选,默认的 classic profile 会在 2027 年初降到 64 天、2028 年降到 45 天。所以你会更频繁地看到续期,但这不改变上面这两条签发限制。
如何判断: 打开 crt.sh,数一下最近 7 天给这个确切名字签了多少张证书。
5. 注册商的 DNSSEC 配错
启用了 DNSSEC,但 DS 记录没传到 registry,或者签名链断了。递归解析器返回 SERVFAIL,CA 做验证查询时和普通人一样查不到。
如何判断:
dig yourdomain.com +dnssec
# 看是否返回 SERVFAIL;或用 https://dnsviz.net 做可视化检查
最短修复路径
Step 1:多地区验证 DNS 解析
别信本地缓存,同时查几个全球 resolver:
dig +short yourdomain.com @8.8.8.8 # Google
dig +short yourdomain.com @1.1.1.1 # Cloudflare
dig +short yourdomain.com @9.9.9.9 # Quad9
想看全球视角就用 whatsmydns.net,它会查大约 20 个节点。期望每个 resolver 都返回宿主要求的 IP / CNAME。如果只有部分返回,说明还在传播:等旧 TTL 过期(一般 1-4 小时;如果上次 TTL 是 86400,最长要 24 小时)。
宿主期望速查(截至 2026 年 6 月;始终以 dashboard 显示的确切值为准):
| 宿主 | 根域名 (@) | 子域名 (www) |
|---|---|---|
| Vercel | A 指向显示的项目 IP(老的 76.76.21.21 仍可用) | CNAME 指向项目值(老的 cname.vercel-dns.com) |
| Cloudflare Pages | CNAME 指向 your-project.pages.dev(根域名用 CNAME flattening) | CNAME 指向 your-project.pages.dev |
| Netlify | A 75.2.60.5 | CNAME 指向 your-project.netlify.app |
| Firebase Hosting | 两条 A 记录(dashboard 显示) | 按 dashboard 用 CNAME 或 A |
| GitHub Pages | 四条 A 记录:185.199.108.153、185.199.109.153、185.199.110.153、185.199.111.153 | CNAME 指向 username.github.io |
Step 2:检查并修 CAA
dig CAA yourdomain.com +short
如果有记录但缺你宿主的 CA,就在 DNS 服务商补上正确的。宿主用 Let’s Encrypt 和/或 Google Trust Services 的话:
yourdomain.com. 3600 IN CAA 0 issue "letsencrypt.org"
yourdomain.com. 3600 IN CAA 0 issue "pki.goog"
yourdomain.com. 3600 IN CAA 0 issuewild "letsencrypt.org"
如果根本没有 CAA 记录,任何 CA 都能签,不用动。CAA 只在注册域名(根域名)这一级检查,子域名继承。
Step 3:别让强制 HTTPS 把验证吃掉
如果 Cloudflare 在代理你的 DNS(橙云开着):
- SSL/TLS → Overview → 在验证期间临时把模式改成 “Flexible” 或 “Off”。(新建的 zone 可能默认是 “Automatic SSL/TLS”,排查时先切到一个固定模式,行为才可预期。)
- SSL/TLS → Edge Certificates → 在证书签好之前,关掉 “Always Use HTTPS” 和 “Automatic HTTPS Rewrites”。
- Rules → 新建一条匹配
yourdomain.com/.well-known/acme-challenge/*的规则,把 Cache 设成 Bypass,并且不重定向到 HTTPS。(Page Rules 仍然能用,但 Cloudflare 在引导大家迁到更新的 Rules → Redirect Rules / Cache Rules,dashboard 里有就优先用新的。)
最省事、最稳的办法:把记录设成 “DNS only”(关掉橙云),等宿主签完证书再开回 proxied。
任何 WAF / 防火墙,都要给 .well-known/acme-challenge/* 加白名单,让验证请求绕过安全规则。
Step 4:在宿主 dashboard 触发重新验证
DNS 和 CAA 都改对之后,宿主不一定会自己重试,手动触发:
- Vercel:Project → Settings → Domains → 域名右侧的
...菜单 → Refresh。 - Cloudflare Pages:进项目 → Custom domains → 域名 → Retry verification。
- Netlify:Domain management → HTTPS → Verify DNS configuration / Renew certificate。
- Firebase:Hosting → 该自定义域名 → 删掉重新添加(最干净的重置)。
盯着 dashboard,验证一过,正常 1-5 分钟内会从 Pending 变 Active。
Step 5:还卡住就查限速和 DNSSEC
# 数最近 7 天给这个确切名字签了多少张证书(浏览器打开)
# https://crt.sh/?q=yourdomain.com
# 查 DNSSEC 是否健康
dig yourdomain.com +dnssec
# 或看可视化报告:https://dnsviz.net/d/yourdomain.com/dnssec/
如果撞了重复证书限速,等满 7 天窗口,或换 challenge type(部分宿主支持 DNS-01,能彻底绕开 HTTP 重定向这个坑)。DNSSEC 坏了就在 registrar 关掉 DNSSEC,等 DS 记录从 registry 清掉,再重新跑验证。
怎么确认真的好了
dashboard 显示 Active 后,别只信那个绿勾,去验证一下真实的证书链:
# 应当打印出有效的 notAfter 日期和 issuer(Let's Encrypt / Google Trust Services)
curl -vI https://yourdomain.com 2>&1 | grep -Ei "issuer|expire|subject"
# 或者直接看证书
echo | openssl s_client -connect yourdomain.com:443 -servername yourdomain.com 2>/dev/null \
| openssl x509 -noout -issuer -subject -dates
正确的结果里:subject 是你的域名,issuer 是已知 CA,notAfter 是一个将来的日期。然后用全新的无痕窗口(避开缓存的重定向)打开 https://yourdomain.com,确认没有证书告警。
常见问题
SSL 签发到底应该多久?
DNS 验证一过,几秒到几分钟。如果 DNS 正确、也没有 CAA 拦截,却超过约 15 分钟还在转,那就不是”还在签”,而是验证在 fail,从 Step 1 重新查起。
我的 DNS 看着没问题,但还在 pending,怎么办?
跑 dig CAA yourdomain.com +short(原因 2)和上面那条 curl 重定向检查(原因 3)。DNS 正确之后还能让你卡住的,就是这两个:一条悄悄拦人的 CAA 记录,或者 .well-known/ 上的强制 HTTPS 重定向。两者在 dashboard 里都不会给出有用的报错。
我必须等 48 小时”DNS 传播”吗?
通常不用。传播的上限是旧记录的 TTL,不是固定的 48 小时。如果上次 TTL 是 300,几分钟就全球生效;最坏的情况是上次 TTL 为 86400(24 小时)。别瞎猜,用 whatsmydns.net 看进度。
签发期间能保留 Cloudflare 的代理(橙云)吗?
能,但要先关掉 “Always Use HTTPS” 和 “Automatic HTTPS Rewrites”,并给 .well-known/acme-challenge/* 加白名单,否则 HTTP-01 验证会被重定向到一个还没生效的 HTTPS 端点。最省事的做法还是先设成 “DNS only”,等证书签好再开回 proxied。
报 “too many certificates already issued”,要等多久才能重试? 这是 Let’s Encrypt 的限速。重复证书限制(同一组主机名 7 天 5 张)按每次签发的滚动 7 天窗口清零。去 crt.sh 看时间戳,等最早那一张过窗口;或者让宿主换一家 CA 签 / 改用 DNS-01。
预防建议
- 加任何新域名之前,先跑
dig CAA yourdomain.com,确认CAA允许宿主的 CA,再去动 dashboard。 - 所有 DNS 记录维护在一个地方(你的 registrar 或一家专业 DNS 服务商),别一半在 Cloudflare、一半在 Route 53。
- 给关键记录设短 TTL(300-1800 秒),紧急切换时不用等几小时传播。
- 在 Cloudflare 后面加域名时,先把记录设成 “DNS only” / 关掉 “Always Use HTTPS”,证书签好后再开回去。
- 监控到期:把上面那条
curl -vI https://yourdomain.com 2>&1 | grep -i expire设成定期任务,提前 30 天告警。(证书有效期在变短,别再默认有 90 天,要确认自动续期真的在跑。)