自定义域名 SSL 一直卡在 Pending:真正的修复办法

在 Vercel / Cloudflare / Netlify 加完域名后 SSL 几小时不变绿?几乎都是 DNS、CAA 记录或 HTTP-01 验证被拦。按这个顺序排查。

你在 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/testHTTP/.. 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.21cname.vercel-dns.com 仍然能解析、仍然有效,但请用你项目 Settings → Domains 页面显示的那个值,一个字符不差地照抄。

如何判断:

dig +short yourdomain.com
dig +short www.yourdomain.com
# 把每一条都和宿主 dashboard 里的确切值比对

2. CAA 记录把宿主的 CA 拦了

域名根上有 CAA 记录,限定了谁能签证书(比如只允许 digicert.com),但你的宿主用的是 letsencrypt.orgpki.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)
VercelA 指向显示的项目 IP(老的 76.76.21.21 仍可用)CNAME 指向项目值(老的 cname.vercel-dns.com
Cloudflare PagesCNAME 指向 your-project.pages.dev(根域名用 CNAME flattening)CNAME 指向 your-project.pages.dev
NetlifyA 75.2.60.5CNAME 指向 your-project.netlify.app
Firebase Hosting两条 A 记录(dashboard 显示)按 dashboard 用 CNAMEA
GitHub Pages四条 A 记录:185.199.108.153185.199.109.153185.199.110.153185.199.111.153CNAME 指向 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 天,要确认自动续期真的在跑。)

相关阅读

标签: #部署 / 托管 #排查 #排查