Custom domains hanging at the TLS validation step

I helped someone solve an frustrating issue with their custom domain creation this morning, and wanted to share the solution for anyone else stumbling upon it. All four domains were stuck on Step 4: “TLS cert creation verification”. Since they worked for a large enterprise software vendor, I suspected corporate DNS restrictions might be the culprit.

The Root Cause: CAA Records

My hunch was right—their CAA (Certificate Authority Authorization) records were blocking Cloudflare from issuing certificates. I recalled Cloudflare using Let’s Encrypt, but a quick look at Add a Custom Domain in the Vendor Portal reminded me that they have a few partners for certificates and may use any of them (thanks @Amber for the nudge toward the docs).

CAA records work to whitelist which certificate authorities are allowed to issue certificates for your domain. Our custom domains are served from Cloudflare, and they use Let’s Encrypt (among other partners) to issue certificates. Since Let’s Encrypt is a good actor, if they aren’t on the list, they don’t issue the certificates.

How we figured out

I started out by getting the CAA records for the company domain.

$ dig sample.io CAA

; <<>> DiG 9.10.6 <<>> sample.io CAA
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 709
;; flags: qr rd ra; QUERY: 1, ANSWER: 10, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;sample.io.                     IN      CAA

;; ANSWER SECTION:
sample.io.              30      IN      CAA     0 issue "globalsign.com"
sample.io.              30      IN      CAA     0 issue "digicert.com"
sample.io.              30      IN      CAA     0 Issuewild "quovadisglobal.com"
sample.io.              30      IN      CAA     0 issue "identrust.com"
sample.io.              30      IN      CAA     0 issue "pki.goog"
sample.io.              30      IN      CAA     0 Issuewild "identrust.com"
sample.io.              30      IN      CAA     0 issue "amazon.com"
sample.io.              30      IN      CAA     0 Iodef "mailto:infosec@cisco.com"
sample.io.              30      IN      CAA     0 issue "quovadisglobal.com"
sample.io.              30      IN      CAA     0 issue "sectigo.com"

;; Query time: 32 msec
;; SERVER: 10.13.6.253#53(10.13.6.253)
;; WHEN: Mon Jun 02 10:44:16 EDT 2025
;; MSG SIZE  rcvd: 467

There are ten records there, but I saw none of them were letsencrypt.org (and some of the other partners are missing). I felt a little surge of victory, but then I remembered something important: CAA records cascade down subdomains. You need to check each level of your domain hierarchy.

So I started checking the subdomains

$ dig security.sample.io CAA

; <<>> DiG 9.10.6 <<>> security.sample.io CAA
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8492
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;security.sample.io.            IN      CAA

;; ANSWER SECTION:
security.sample.io.     30      IN      CAA     0 issue "amazon.com"
security.sample.io.     30      IN      CAA     0 issue "identrust.com"
security.sample.io.     30      IN      CAA     0 issue "letsencrypt.org"

;; Query time: 41 msec
;; SERVER: 10.13.6.253#53(10.13.6.253)
;; WHEN: Mon Jun 02 10:46:49 EDT 2025

Interestingly, this level does allow Let’s Encrypt, which I thought a the time was all that I needed.

security.sample.io.     30      IN      CAA     0 issue "letsencrypt.org"

I though I might be runnuing down the wrong path, but I had to keep that cascading in mind.

$ dig product.security.sample.io CAA

; <<>> DiG 9.10.6 <<>> product.security.sample.io CAA
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 59261
;; flags: qr rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;product.security.sample.io.  IN      CAA

;; ANSWER SECTION:
product.security.sample.io. 30 IN     CAA     0 issue "identrust.com"
product.security.sample.io. 30 IN     CAA     0 issuewild "amazon.com"
product.security.sample.io. 30 IN     CAA     0 issuewild "amazonaws.com"
product.security.sample.io. 30 IN     CAA     0 issuewild "amazontrust.com"
product.security.sample.io. 30 IN     CAA     0 issuewild "awstrust.com"

;; Query time: 18 msec
;; SERVER: 10.13.6.253#53(10.13.6.253)
;; WHEN: Mon Jun 02 10:48:37 EDT 2025
;; MSG SIZE  rcvd: 371

Aha! This level blocks Let’s Encrypt entirely. Based on what I’ve seen with my custom domains in the past, No Let’s Encrypt = no certificates. Just to be complete, I checked one of the specific domains and saw there were not CAA certificates for it.

The Solution

Changing enterprise DNS records can be a nightmare, especially when security gets involved. For minimum impact (an hopefully a quicker review turnaround), I recommended adding host-specific CAA records for all four custom domains. If you’re in a smaller company, you may be able to update one of the subdomain levels or even your TLD.

To cover all the potential issuers, they need the records in the following example zone file:

$ORIGIN product.security.sample.io
$TTL 3600

charts        IN  CAA 0 issue "letsencrypt.org"             ; registry
charts        IN  CAA 0 issue "pki.goog; cansignhttpexchanges"
charts        IN  CAA 0 issue "ssl.com"
charts        IN  CAA 0 issue "amazon.com"
charts        IN  CAA 0 issue "cloudflare.com"
charts        IN  CAA 0 issue "google.com"
charts        IN  CAA 0 iodef "mailto:security@sample.io"   ; consistent with the TLD

images        IN  CAA 0 issue "letsencrypt.org"             ; proxy service  
images        IN  CAA 0 issue "pki.goog; cansignhttpexchanges"
images        IN  CAA 0 issue "ssl.com"
images        IN  CAA 0 issue "amazon.com"
images        IN  CAA 0 issue "cloudflare.com"
images        IN  CAA 0 issue "google.com"
images        IN  CAA 0 iodef "mailto:security@sample.io"   ; proxy service

enterprise    IN  CAA 0 issue "letsencrypt.org"             ; enterprise portal
enterprise    IN  CAA 0 issue "pki.goog; cansignhttpexchanges"
enterprise    IN  CAA 0 issue "ssl.com"
enterprise    IN  CAA 0 issue "amazon.com"
enterprise    IN  CAA 0 issue "cloudflare.com"
enterprise    IN  CAA 0 issue "google.com"
enterprise    IN  CAA 0 iodef "mailto:security@sample.io"   ; enterprise portal

updates       IN  CAA 0 issue "letsencrypt.org"             ; app service
updates       IN  CAA 0 issue "pki.goog; cansignhttpexchanges"
updates       IN  CAA 0 issue "ssl.com"
updates       IN  CAA 0 issue "amazon.com"
updates       IN  CAA 0 issue "cloudflare.com"
updates       IN  CAA 0 issue "google.com"
updates       IN  CAA 0 iodef "mailto:security@sample.io"   ; app service

If your custom domains are stuck on certificate verification, grab your favorite DNS tool and start _dig_ging for CAA records. You might just find your answer buried in corporate DNS policies.