Custom Domain SSL Provisioning Stuck

SSL pending for hours after domain hookup — usually DNS or CAA.

You added a custom domain on Vercel / Cloudflare Pages / Netlify / Firebase. The dashboard says “Provisioning SSL certificate”, “Issuing certificate”, or “Pending validation” — and hours later (or even a day or two) it’s still not green. Visiting https://yourdomain.com returns NET::ERR_CERT_AUTHORITY_INVALID or just won’t connect. Most hosts use Let’s Encrypt (or Google Trust Services) underneath, and certificate issuance itself takes seconds. When it stalls it’s almost always wrong DNS, a CAA record blocking the CA, or the host’s HTTP-01 challenge being blocked by your forced-HTTPS / WAF rules. This article runs the checks in DNS → CAA → host-specific order.

Common causes

Ordered by hit rate.

1. DNS hasn’t propagated, or was pointed wrong

The most common. After adding the A / CNAME at the registrar, TTL hasn’t expired yet, or the record type / value is wrong (apex domain can’t be a CNAME but was set to one; A points at a stale IP). The host can’t verify domain ownership.

The host dashboard usually shows the exact expected values — e.g. Vercel wants an apex A record to 76.76.21.21 and www CNAME to cname.vercel-dns.com.

How to spot it:

dig +short yourdomain.com
dig +short www.yourdomain.com
# Compare against the host's expected IP / CNAME

2. CAA record blocks the host’s CA

The apex has a CAA record restricting who can issue certs (e.g. only digicert.com), but the host issues from letsencrypt.org or pki.goog. The CA reads CAA, refuses to issue, and the host sits in Pending forever.

Typical trigger: migrated from an enterprise DNS platform and the old CAA records came along.

How to spot it:

dig CAA yourdomain.com +short
# Example output: 0 issue "digicert.com"
# If letsencrypt.org / pki.goog is not in the allow list, you need to add it

3. HTTP-01 challenge blocked by forced HTTPS / WAF

Let’s Encrypt defaults to HTTP-01: it drops a file at http://yourdomain.com/.well-known/acme-challenge/TOKEN and fetches it. If you have Cloudflare’s “Always Use HTTPS” on or SSL mode set to “Full (strict)”, the HTTP request gets 301’d to HTTPS — but HTTPS isn’t ready yet, and the challenge fails.

WAF / firewall rules blocking .well-known/ cause the same issue.

How to spot it:

curl -I http://yourdomain.com/.well-known/acme-challenge/test
# If it returns 301 → https, the redirect is killing the challenge

4. Wildcard / old cert conflict

The domain previously had a wildcard *.yourdomain.com issued by another host, or multiple projects share the same subdomain and issuance requests collide. Vercel occasionally reports Failed to issue certificate: too many certificates already issued for this exact set of domains (Let’s Encrypt rate limit: 5 per exact set per 7 days).

How to spot it: Search crt.sh for the domain and count how many certs have been issued in the last 7 days.

5. DNSSEC misconfigured at the registrar

DNSSEC is enabled but the DS record never made it to the registry, or the signing chain is broken. Recursive resolvers get SERVFAIL and the CA’s validation also fails.

How to spot it:

dig yourdomain.com +dnssec
# Check for SERVFAIL; or use https://dnsviz.net for a visual check

Shortest path to fix

Step 1: Check DNS from multiple regions

Use multiple global resolvers — don’t trust your local cache:

# Command line
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 tool (recommended)
# https://www.whatsmydns.net/  ~20 nodes globally

Every resolver should return the host’s expected IP / CNAME. If only some do, propagation is still in progress — wait for TTL to expire (usually 1-4 hours; if previous TTL was 86400, up to 24 hours).

Host-expected records quick reference:

HostApex (@)Subdomain (www)
VercelA 76.76.21.21CNAME cname.vercel-dns.com
Cloudflare PagesCNAME flattening / ACNAME your-project.pages.dev
NetlifyA 75.2.60.5CNAME your-project.netlify.app
Firebase HostingTwo A records (shown in dashboard)CNAME or A
GitHub PagesFour A records (185.199.108.153, etc.)CNAME username.github.io

Step 2: Inspect and fix CAA

dig CAA yourdomain.com +short

If records exist but exclude your host’s CA, add at the DNS provider:

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"

If no CAA records exist at all, any CA can issue — no change needed. CAA is checked at the registered domain (apex); subdomains inherit.

Step 3: Temporarily disable forced HTTPS / allow .well-known/

If you’re using Cloudflare as DNS + proxy:

  • SSL/TLS → Overview → temporarily switch to “Flexible” or “Off” (during validation only)
  • Rules → Page Rules → add: yourdomain.com/.well-known/* → Cache Level: Bypass + SSL: Flexible

Or just disable Cloudflare’s orange cloud (DNS only) until the host completes issuance, then re-enable proxied.

WAF rules: whitelist .well-known/acme-challenge/* to bypass all challenge / firewall checks.

Step 4: Trigger re-validation in the host dashboard

Once DNS and CAA are fixed, the host won’t always retry on its own. Force it:

  • Vercel: Project → Settings → Domains → ⋯ menu → Refresh
  • Cloudflare Pages: Custom domains → domain → Retry verification
  • Netlify: Domain settings → HTTPS → Renew certificate
  • Firebase: Hosting → Custom domains → remove and re-add (cleanest)

Watch the dashboard — it should flip from Pending to Active within 1-5 minutes.

Step 5: Still stuck? Check issuance rate limits and DNSSEC

# Count certs issued in the last 7 days (Let's Encrypt: 5 per exact set per 7 days)
# Open in browser
https://crt.sh/?q=yourdomain.com

# Check DNSSEC health
dig yourdomain.com +dnssec
# Or https://dnsviz.net/d/yourdomain.com/dnssec/

Rate-limited: wait 7 days or switch challenge type (some hosts support DNS-01). DNSSEC errors: disable DNSSEC at the registrar and rerun.

Prevention

  • Before adding any new domain, dig CAA yourdomain.com and confirm CAA allows the host’s CA before touching the dashboard
  • Keep all DNS records in one place (registrar or one dedicated DNS provider) — don’t split half on Cloudflare and half on Route 53
  • Set short TTL (300-1800s) for critical domains so emergency switches don’t take hours to propagate
  • When adding a domain, turn off Cloudflare proxy / Always HTTPS first; turn them back on after the cert is issued
  • Monitor cert expiry: schedule curl -vI https://yourdomain.com 2>&1 | grep expire and alert at 30 days remaining

Tags: #Hosting #Debug #Troubleshooting