# The DNS Scan That Looked Fine Until It Went Live
Two domains, both carrying live mail, both about to move into our Cloudflare account. On 2026-07-29 we ran the "Add a site" DNS scan, looked at the imported records, and thought: great, that's most of the work done. Then we found it wasn't something we could trust. Detected and fixed the same day, which is the only reason I'm writing this up as a process that worked rather than a disaster.
Symptom
The scan imported a handful of mail and service CNAMEs as proxied. The names were autodiscover, email, lyncdiscover, msoid and sip. All five came in with the orange cloud switched on.
While the zone is pending, that's harmless. Nothing resolves through Cloudflare yet, so a proxied flag is just a flag sitting in a dashboard. The trouble is what happens on activation: those names start resolving to Cloudflare IPs instead of the mail host they were meant to point at. Outlook autodiscover breaks. Lync and SIP clients break. Nobody sees it until the nameservers flip.
The second symptom was quieter. Look at the TXT records after the scan and you'd find values stored wrapped in literal quotes. And after our own add-if-missing pass, the zone held two SPF records. Two. That's a double-SPF permerror in waiting, and receivers treat a permerror as a failed SPF check, which is a bad outcome for a domain whose whole job is carrying mail.
What we thought it was
Our first guess was our own script. We'd written an add-if-missing pass to fill gaps after the scan, so a duplicate SPF record pointed straight at us: a loop that ran twice, probably, or a retry that didn't check state. That diagnosis was believable because it was true of so many earlier bugs. We know our own scripts better than we know anyone else's behaviour, and "we wrote a loop that ran twice" is the kind of mistake that's easy to make and easy to fix.
For the proxied CNAMEs, the wrong theory was a default. Surely Cloudflare proxies CNAMEs by default, we said, and we'd just forgotten to turn it off on the mail ones. Also believable, because it half describes the product. Proxying is the headline feature, and for website hostnames it's what you want.
But neither explanation fit once we looked properly. The script ran once. And the proxied flag wasn't something we'd left at a default; the scan had set it on exactly the names that should never have been proxied. That's a different class of problem.
Root cause
Two separate behaviours, both inside the "Add a site" scan, both invisible while the zone is pending.
First, the scan marks mail and service CNAMEs as proxied. autodiscover, email, lyncdiscover, msoid and sip all point at services that speak protocols Cloudflare's HTTP proxy doesn't carry. Proxy them and the hostname resolves to a Cloudflare edge address, so traffic that should reach the mail or Lync host lands somewhere that can't answer it. A pending zone hides this entirely, which is why a visual check of the imported records tells you nothing.
Second, the scan stores TXT values wrapped in literal quotes. The quote marks are part of the stored string. Our add-if-missing pass compared content exactly. It asked whether a record with the unquoted SPF value already existed, got "no" because the stored version had quotes around it, and wrote a fresh unquoted copy. Result: two SPF records, one quoted, one not, both the same policy.
So the duplicate was our script's doing, but the cause was upstream. Exact-match comparison against a value that had been silently altered will always miss. The bug wasn't a loop. It was an assumption that the data we read back was the data we'd have written.
The fix
The fix script does two things.
It sets proxied to false on every non-website CNAME. That covers autodiscover, email, lyncdiscover, msoid and sip, and anything else that isn't actually serving a web page. Website hostnames keep their proxy setting; the rule is about what the name is for, not a blanket switch.
It also dedupes TXT records on their unquoted value. Strip the wrapping quotes, compare what's left, keep one. That removed the second SPF record and, more to the point, stops the add-if-missing logic from being fooled by quoting again.
Alongside the fix we added a pre-flip verifier. Before any nameserver change, it diffs MX, TXT, DMARC, CNAME and SRV between Cloudflare and the registrar. Anything that differs is a stop. That check would have caught both problems on its own, before activation rather than after.
Why it happened
It survived review because every individual step looked reasonable. The scan produced records. The records looked right in the dashboard. Our script filled gaps. Nobody was reading the zone as the resolver would, because while a zone is pending the resolver never sees it.
That's the systemic part. Review checked that the records existed, not that they behaved. A proxied flag has no visible consequence until activation, and a quoted TXT value looks identical to an unquoted one at a glance. We were reviewing a picture of the zone instead of its effect, and the picture was fine.
We'd also leaned on the scan as a source of truth. It's a convenience import, a first draft, and we treated it as a finished one.
How to avoid it
If you're moving a zone that carries live mail, treat the automated scan as a suggestion and nothing more. Compare it against the authoritative records at your current provider before you touch nameservers, record type by record type. MX, TXT, DMARC, CNAME and SRV are the ones that quietly hurt.
Never leave mail or service CNAMEs proxied. Autodiscover, SIP and similar names need to resolve to the real host, so grey-cloud them on purpose.
And when you write idempotent add-if-missing logic, normalise before you compare. Strip quotes, trim whitespace, lower-case where the type allows it. Exact matching feels safe and it's exactly how you get two SPF records.
Anyway, that's the lot. The scan was fine at being a scan. We just asked it to be a verifier.
Every commit, file reference and date above is checked
against this incident's Noctem evidence pack before publication. Evidence:
cloudflare-add-a-site-proxies-mail-cnames-double-writes-txt.