在 Vercel / Cloudflare Pages / Netlify / Firebase 上把自定义域名加进去之后,dashboard 一直显示 “Provisioning SSL certificate”、“Issuing certificate” 或 “Pending validation”,几小时甚至一两天都不变绿——访问 https://yourdomain.com 浏览器报 NET::ERR_CERT_AUTHORITY_INVALID 或直接连不上。多数宿主背后都是 Let’s Encrypt(或 Google Trust Services),证书签发本身只需要几十秒,卡住几乎一定是 DNS 没指对、CAA 把 CA 拦了、或者宿主用 HTTP-01 challenge 但你开了强制 HTTPS 重定向阻断验证。本文按”先 DNS、再 CAA、再宿主特有问题”的顺序排查。
常见原因
按命中率从高到低排。
1. DNS 还没传播,或者根本指错了
最常见。在域名服务商加 A / CNAME 后,TTL 没到、或者类型 / 值填错(比如根域名只能 A 不能 CNAME,但写了 CNAME;或 A 指向了过时的 IP),宿主无法验证域名归属。
宿主 dashboard 里通常会显示具体期望值,例如 Vercel 要求根域名 A 记录指向 76.76.21.21,子域名 CNAME 指向 cname.vercel-dns.com。
如何判断:
dig +short yourdomain.com
dig +short www.yourdomain.com
# 比对宿主期望的 IP / CNAME
2. CAA 记录把宿主的 CA 拦了
域名根上有 CAA 记录限定了能签证书的 CA(如只允许 digicert.com),但宿主用的是 letsencrypt.org 或 pki.goog,CA 一查 CAA 直接拒绝签发,宿主就一直停在 Pending。
典型情况:从企业 DNS 平台迁过来,老 CAA 记录留着没改。
如何判断:
dig CAA yourdomain.com +short
# 输出示例:0 issue "digicert.com"
# 若不包含 letsencrypt.org / pki.goog,需要补
3. HTTP-01 challenge 被强制 HTTPS / WAF 拦住
Let’s Encrypt 默认用 HTTP-01:在 http://yourdomain.com/.well-known/acme-challenge/TOKEN 放一个验证文件。如果你在 Cloudflare 开了 “Always Use HTTPS” 或 SSL 模式设成 “Full (strict)“,HTTP 请求被强制 301 到 HTTPS,但 HTTPS 还没好,验证就 fail。
WAF / 防火墙规则拦 .well-known/ 路径也是同类问题。
如何判断:
curl -I http://yourdomain.com/.well-known/acme-challenge/test
# 如果是 301 → https,说明被强制跳了
4. 通配符证书 / 老证书冲突
域名之前在另一家宿主已经签过通配符 *.yourdomain.com,或者多个项目同时绑同一个子域名,签发请求互相覆盖。Vercel 偶尔会报 Failed to issue certificate: too many certificates already issued for this exact set of domains(Let’s Encrypt 7 天限速 5 张)。
如何判断:去 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
# Web 工具(建议)
# https://www.whatsmydns.net/ 全球 ~20 个节点
期望所有 resolver 都返回宿主要求的 IP / CNAME。如果只有部分返回,就是还在传播,等 TTL 过期(一般 1-4 小时;上次 TTL 是 86400 则要 24 小时)。
宿主期望速查:
| 宿主 | 根域名 (@) | 子域名 (www) |
|---|---|---|
| Vercel | A 76.76.21.21 | CNAME cname.vercel-dns.com |
| Cloudflare Pages | CNAME flattening / A | CNAME your-project.pages.dev |
| Netlify | A 75.2.60.5 | CNAME your-project.netlify.app |
| Firebase Hosting | A 两条(dashboard 显示) | CNAME 或 A |
| GitHub Pages | A 四条 (185.199.108.153 等) | CNAME username.github.io |
Step 2:检查 + 修 CAA 记录
dig CAA yourdomain.com +short
如果有记录但缺你的 CA,在 DNS 服务商加:
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 只看根域名(registered domain),子域名继承。
Step 3:临时关闭强制 HTTPS / 放行 .well-known/
如果用 Cloudflare 当 DNS + proxy:
- SSL/TLS → Overview → 暂时改成 “Flexible” 或 “Off”(仅验证期间)
- Rules → Page Rules → 新建:
yourdomain.com/.well-known/*→ Cache Level: Bypass + SSL: Flexible
或者直接关掉 Cloudflare 的 orange cloud(DNS only),等宿主把证书签下来再开回 proxied。
WAF 规则:在 .well-known/acme-challenge/* 路径上加白名单,绕过所有 challenge / firewall 检查。
Step 4:在宿主 dashboard 触发重新验证
DNS 和 CAA 都修好之后,宿主不一定会立刻重试。手动触发:
- Vercel:Project → Settings → Domains → 域名右侧 ⋯ → Refresh
- Cloudflare Pages:Custom domains → 域名 → Retry verification
- Netlify:Domain settings → HTTPS → Renew certificate
- Firebase:Hosting → Custom domains → 删了重新添加(最干净)
观察 dashboard 状态,正常情况 1-5 分钟内会从 Pending 变 Active。
Step 5:还卡住就排查证书签发限速 + DNSSEC
# 查最近 7 天签发了多少张同名证书(Let's Encrypt 限 5 张 / 7 天)
# 浏览器打开
https://crt.sh/?q=yourdomain.com
# 查 DNSSEC 是否健康
dig yourdomain.com +dnssec
# 或用 https://dnsviz.net/d/yourdomain.com/dnssec/
如果触了限速,等 7 天或换 challenge type(部分宿主支持 DNS-01);DNSSEC 报错就在 registrar 关掉 DNSSEC 重新跑一次。
预防建议
- 加新域名之前先
dig CAA yourdomain.com,确认 CAA 允许宿主的 CA,再去 dashboard 操作 - 所有 DNS 记录维护在一个地方(registrar 或一家专业 DNS 服务商),别一半在 Cloudflare 一半在 Route 53
- 给关键域名 TTL 设短(300-1800 秒),紧急切换时不用等几小时传播
- 加完域名先把 Cloudflare proxy / Always HTTPS 关掉,等证书签好再开
- 监控证书到期:
curl -vI https://yourdomain.com 2>&1 | grep expire设定期任务,提前 30 天告警