A customer emailed in saying his site was dead on his phone. Mobile Safari, “DNS Resolution Error,” nothing loading. The day before, I’d flipped a batch of domains onto the new Cloudflare edge as part of a migration I’d been grinding on for weeks. So the timing told me everything I needed to know about what I’d broken. What it didn’t tell me was how many other customers were sitting in the same broken state, quietly, not emailing me.
That second question is the whole post. The outage was real and it was my fault. What turned it from a multi-day support slog into a 30-minute fix was a scan I’d built during the migration, before I needed it, that sized the damage in about 12 seconds: 8 domains, not the unknowable number I’d have feared.
The error that lies about itself
The customer’s symptom said DNS. Mobile Safari said “DNS Resolution Error.” Every instinct says go check the DNS records.
The DNS was fine. The domain’s CNAME was pointing at the Cloudflare edge exactly like it was supposed to. What was actually happening: Cloudflare received the connection, went to route it through SSL for SaaS, looked for a custom hostname object matching that domain, and didn’t find one. That’s Cloudflare error 1001, custom hostname not found. The user-facing browser dressed it up as a DNS failure, but DNS had done its job perfectly. The traffic arrived at Cloudflare. Cloudflare just had no record telling it where that hostname should go.
If you run Cloudflare SSL for SaaS for multi-tenant custom domains, error 1001 is the one you will misdiagnose first, every time, because the browser blames DNS and the DNS is correct. The fix is never a DNS change. The fix is: register the custom hostname in your Cloudflare zone. The CNAME pointing at your zone is necessary but not sufficient. You also have to create the custom hostname object so the edge knows how to terminate and route it.
For this specific customer the gap was obvious in hindsight. His domain was one of a set of legacy www redirect domains pointed at the production app. These are old customer domains that just bounce www to the main app. When I flipped the edge, the www CNAMEs for these redirect domains hadn’t been registered as Cloudflare custom hostnames yet. DNS now pointed at Cloudflare, Cloudflare had nothing to match, 1001.
So I knew the failure class. One customer, one domain, one root cause. The dangerous question was the next one.
How many more are sitting like this
This is the moment a migration goes one of two ways.
The bad way: you fix the customer who emailed, close the ticket, and wait. Every other domain in the same broken state is a future support ticket you haven’t received yet, arriving on its own schedule, each one re-investigated from scratch because you’ve already mentally filed the first one as “handled.” Per-domain triage. You respond to the same incident ten times.
The good way needs a tool that already exists at the moment the first ticket lands. Mine did, barely, because I’d built a lifecycle scan during the migration project itself, not after this outage. The scan walks every domain in the migration and classifies its state. I pointed it at exactly the failure mode the customer hit: domains whose www CNAME resolves into the Cloudflare zone but has no matching custom hostname registered.
It came back with 8.
Eight domains, same class: legacy redirect domains never registered as custom hostnames. All fixable the identical way, by registering the missing hostname in the Cloudflare zone. One class, one remediation, eight applications, restored as a batch. No per-domain re-investigation.
Smaller than any number I’d have guessed before the migration. If you’d asked me cold “how bad is it if the edge flip goes wrong,” the honest pre-migration answer was “I don’t know, somewhere between one domain and all of them,” which is the answer that makes you afraid to flip at all. The real answer was 8, bounded and known, because the scan could enumerate the class instead of me imagining it.
The one false positive that matters
The scan flagged a ninth domain. It looked like the same failure on first read. It wasn’t. That domain was sitting on its own separate Cloudflare zone, not the SSL for SaaS multi-tenant zone, so the custom-hostname logic didn’t apply to it at all. False positive.
I’m calling this out on purpose, because a blast-radius scan that returns zero false positives is usually a scan that’s also missing real cases. The useful version is slightly over-inclusive and forces you to eyeball each hit, rather than one tuned so tight it quietly drops the edge cases you most need to see. One false positive out of nine is a scan I trust. Nine perfect hits with no noise would have made me wonder what it wasn’t showing me.
Why the scan could answer in 12 seconds
The scan being fast is not a side detail. It’s the reason it was usable mid-incident.
An earlier version of this same scan was timing out. It was trying a Let’s Encrypt certificate issuance probe against every domain to check TLS health, including the 37 domains whose apex A record hadn’t been flipped to the new relay yet. An LE probe against a domain that isn’t pointing at the relay always fails, and it fails slowly. Slow failure times a few dozen domains is a scan that takes long enough that you don’t reach for it during a live outage.
The fix was a precondition guard: only attempt the relay TLS probe if the apex A record already resolves to the relay IP. Skip the expensive probe when the precondition can’t be met. With that guard in, the scan ran 39 migrated domains in about 12 seconds and silently skipped the 37 that weren’t ready. That’s the difference between a tool you run while the customer waits and a tool you’d rather not run at all.
Building it taught me that probing external systems as part of a scan feels free until you’re doing it at scale against things that aren’t ready to answer; the happy-path unit test never catches that, because in the test the precondition is always met. The scan also had to run off-box. Run from the Windows application server, the relay TLS probe fails with an SSPI error, because Windows integrated auth tries to negotiate on an outbound connection to a Caddy relay running on Linux that wants none of it. Off-box vantage, every time.
The guard that made it fast is three lines: resolve the apex, bail if it isn’t the relay IP, only then probe.
Related
- The False 200: When Removing a Binding Reveals Your Verification Was Lying: plausible-looking success states that hide a real failure
- Cloudflare Error 1014: The Cert Issued. The Page Didn’t Load.: the CF error class that rewrites architecture assumptions mid-migration
- An Off-Origin Caddy Relay for Bare-Domain SSL (Because Cloudflare Pro Won’t Proxy Your Apex): the relay architecture this migration was building toward
- Route a Change by Its Blast Radius, Not Its Size or Your Schedule: the general principle behind per-domain rollout as a safety mechanism
- The Cloudflare-for-SaaS + IIS Gotcha Nobody Documents: SNI vs Host Header: another CF SaaS failure class encountered during the same migration