Migrating a WordPress Fleet, Part 2: DNS Architecture, Why Cloudflare and How We Set It Up

This is part 2 of an 8-part series on migrating a fleet of roughly 40 WordPress sites from two shared-hosting providers (A2 Hosting and SiteGround) onto a self-managed Hestia Control Panel VPS with Cloudflare as the DNS layer. “Self-managed Hestia Control Panel VPS” does not imply exotic or special-purpose hardware: Hestia runs on any generic x86_64 Linux box. Our own instance is a Hetzner Cloud CPX32 (4 vCPU, 8GB RAM, roughly 160GB SSD disk, running Ubuntu 24.04), reachable over Hetzner’s standard shared datacenter network like any other cloud VPS, not a dedicated line or special connection.

This series is written for the sysadmin who is considering doing this themselves and wants to know exactly how hard it actually is before they start.

Part 2: DNS architecture: why Cloudflare, and how we set it up

Why Cloudflare specifically

The proximate reason was Let’s Encrypt. Issuing an HTTP-01 challenge certificate for dozens of domains during an active migration window needs DNS that resolves reliably and that you can verify quickly, and that isn’t itself in the middle of moving. Self-hosted DNS (Hestia ships its own bind9) works fine for a handful of domains you fully control, but it adds one more moving part you have to keep correct across a fleet, and it doesn’t give you the tooling Cloudflare does: a dashboard-visible zone status, a DNS-over-HTTPS endpoint you can query programmatically (cloudflare-dns.com/dns-query) without worrying about local resolver cache staleness, and Redirect Rules for domains that don’t need real hosting at all (Part 7).

Cloudflare’s free tier covers all of this. We used DNS-only mode (the “grey cloud,” meaning Cloudflare answers DNS queries but does not proxy HTTP traffic) as the default for every domain with real hosted content, and reserved Proxied mode (orange cloud) for the one case that actually requires it: Redirect Rules, which only fire on proxied traffic.

The “no exceptional configurations” policy

Partway through the project we adopted an explicit standard: zero domains should have live DNS hosted on Hestia’s own bind9. Every domain’s real, authoritative DNS should live at its registrar-facing DNS provider, which for this fleet meant Cloudflare, full stop. This sounds obvious in hindsight but it wasn’t automatic. An audit turned up six domains, plus one unrelated one discovered along the way, still carrying live DNS zones on Hestia’s bind9, leftover from earlier ad hoc work. Every one of them, when checked against the real registrar-level nameservers, turned out to already be served from Cloudflare or from a different provider entirely; the Hestia-hosted zones were 100% inert. We deleted them all with v-delete-dns-domain, verified with named-checkconf, and confirmed zero impact on live resolution.

The lesson generalizes: if you’re running a control panel that offers to host DNS for you, and you have a separate, better DNS provider, actively audit for and kill the panel’s own DNS zones. They rot silently and nobody notices until an audit like this one goes looking.

Setting up a new zone: what auto-scan gets wrong

Adding a domain to Cloudflare triggers an auto-scan that imports existing records from the old DNS provider. It’s a good starting point, not a complete one. Twice, on two different domains, auto-scan silently missed a DKIM-style TXT record on a non-standard subdomain (default._domainkey, and an A record for a subdomain used by an outbound mail relay). Both times this was caught by diffing the auto-scanned zone against a manually-verified copy of the old zone, not by trusting the scan. Build that diff step into your own checklist. Auto-scan is a convenience, not an audit.

Once the zone exists, we used Cloudflare’s bulk record editor (select-all, then “Edit N records”) to flip every proxy-eligible A/CNAME record from the auto-scan’s default of Proxied to DNS-only in one action, rather than editing records one at a time. For a domain carrying a dozen mail- and service-related subdomains (autoconfig, autodiscover, mail, ftp, ssh, and so on), that’s a meaningful time saver.

Nameserver pairs are not a fixed constant, and this bit us more than once

This caused the most real, visitor-facing downtime in the whole project. Cloudflare assigns each zone a pair of nameservers from its pool (we saw jessica.ns.cloudflare.com/kip.ns.cloudflare.com on some zones, boyd.ns.cloudflare.com/laila.ns.cloudflare.com on others, both under the same Cloudflare account). This pair is per-zone, not per-account, and it is not something you can predict or reuse from a previous domain.

More than once, a domain’s registrar-level nameservers were pointed at the wrong pair, one that belonged to a different zone entirely within the same Cloudflare account. The result is not a soft failure. Cloudflare’s edge has no matching zone to answer for that domain’s nameservers, so real visitors get SERVFAIL or “No Reachable Authority,” full stop, until someone notices and corrects the registrar record to the pair that specific zone actually wants. This happened on multiple domains during this project, sometimes introduced by a well-intentioned but slightly-wrong manual change someone made after being told “point it at Cloudflare.”

The fix is always the same: open the specific domain’s zone in the Cloudflare dashboard, find its actual assigned nameserver pair (Overview page, or the nameserver-instructions panel for a not-yet-active zone), and verify the registrar matches that, not a pair copied from another domain you fixed last week. Never assume a nameserver pair is fleet-wide.

Verifying propagation without fooling yourself

DNS propagation checks need more than one source, because results genuinely disagree during a transition window, and each source lags differently:

  • RDAP (rdap.org, or a domain’s registry directly) reflects the true registry state fastest, often within minutes of a registrar-level change.
  • A fresh DNS-over-HTTPS query against a specific public resolver (cloudflare-dns.com/dns-query, or Google’s 8.8.8.8) avoids the false negatives you get from a local resolver’s cache.
  • Cloudflare’s own dashboard status (“Active” vs “Moved”) can lag behind RDAP by hours, since it runs its own propagation-check cadence; there’s a manual “Check nameservers now” button to force a recheck, but it can still show “Moved” for a domain that is, in every practical sense, already resolving and serving correctly. Don’t treat a stale dashboard status as proof of a problem if RDAP, DoH, and a live curl all agree the domain works.

We also saw the inverse mistake: trusting a single curl from one network as ground truth. A resolver’s cache lag after a fresh cutover can make a domain look broken for a few minutes when it’s actually fine; always cross-check with an authoritative source (DoH, or curl with --resolve to force the IP you expect) before concluding there’s a real bug.

The registrar cutover checklist

For each domain moving its real hosting to the new VPS:

  1. Add the zone to Cloudflare (let auto-scan run, then diff against the old zone).
  2. Set the root A record and the www record to point at the new server’s IP, DNS-only.
  3. Leave every other service record (cpanel., webmail., whm., webdisk., ftp., and the mail-related ones) pointed at the old host’s IP for now, but still switch their proxy status to DNS-only. You’ll deal with mail in its own pass; see Part 5.
  4. Check whether mail.<domain> is a CNAME pointing at the bare root domain before you touch anything. This is a common cPanel default, and if you miss it, it silently rides along the moment you repoint the root A record, breaking any mail client configured against that hostname even though MX-routed mail (if it goes elsewhere, like Google Workspace or iCloud) keeps working fine. Convert it to a static A record at the old host’s IP first if you’re not migrating mail in the same pass.
  5. Once DNS is confirmed live via a real DoH query, issue SSL (Part 4).
  6. Only after both of the above are solid, decide whether and when to actually cut mail over (Part 5). Don’t couple the web cutover and the mail cutover into one irreversible step.

Leave a Comment