Playbook
Migrating hundreds of domains to Cloudflare without downtime
Large migrations rarely fail on the technology. They fail on the record nobody knew about, the origin firewall that still blocks the new edge, or a rollback that takes two days because it depends on nameserver caches. This is the staged approach I use to keep each of those boring.
1. Inventory before anything moves
Every domain gets exported and classified before it is scheduled. For a portfolio of hundreds of zones this is scripted against the current provider's API, not done by hand.
- Records: full zone export, with each hostname tagged as web, API, mail, verification (TXT) or unknown. "Unknown" is a blocker, not a footnote.
- Mail: MX targets and the hosts behind them stay DNS-only. Cloudflare's HTTP proxy does not carry SMTP, and proxying the A record an MX points to breaks inbound mail.
- DNSSEC: if it is enabled at the registrar, it has to be disabled (or moved to a multi-signer setup) before the nameserver change, otherwise validating resolvers fail the whole zone.
- CAA: existing CAA records must allow the certificate authorities Cloudflare issues from, or edge certificates will not issue.
- Origins: which hosts allow-list client IPs, rate-limit per source IP, or log client addresses. All of them will start seeing Cloudflare addresses instead.
2. Build the baseline once, apply it everywhere
Each zone lands on a known configuration instead of being tuned individually. The baseline lives in Terraform, and existing Cloudflare configuration is imported with cf-terraforming so nothing is changed by surprise.
- SSL/TLS mode Full (strict), with an Origin CA or public certificate on every origin. Flexible mode is never a target state.
- Minimum TLS version, Always Use HTTPS and automatic HTTPS rewrites set consistently.
- Cache rules for static paths, with the origin's
Cache-Controltreated as the contract. - WAF managed rulesets and the account-level custom rules every zone inherits.
- HSTS held back until every hostname in the zone is confirmed to serve HTTPS. A long
max-agewithincludeSubDomainsis hard to undo.
3. Move DNS first, proxy second
This is the part that makes zero-downtime realistic. The nameserver change and the traffic change are two separate events:
- Lower TTLs at the old provider 24 to 48 hours ahead.
- Import the zone into Cloudflare with every record DNS-only. Resolution is identical to before, just answered by different nameservers.
- Change nameservers at the registrar and wait for the zone to go active.
- Confirm edge certificates are issued for every hostname that will be proxied. Universal SSL covers the apex and one level of subdomain; deeper names need Advanced Certificates.
- Open origin firewalls to Cloudflare's published IP ranges.
- Only then switch records to proxied, in waves.
Why it matters: once the zone is on Cloudflare, rolling a hostname back is a single toggle from proxied to DNS-only. Rolling back a nameserver change depends on registry and resolver caches and can take days.
4. Waves, not a big bang
Domains are grouped by risk and traffic, and each wave has a written go/no-go and rollback step.
| Wave | Scope | Gate to proceed |
|---|---|---|
| 0 - Pilot | Internal and parked domains | Baseline applies cleanly, no 52x errors |
| 1 - Low risk | Marketing and static sites | Cache hit ratio and error rates stable for 48 h |
| 2 - Business | Customer-facing apps | Owner sign-off, security events reviewed |
| 3 - Critical | Login, payment, APIs | Change window, owners on the call, rollback rehearsed |
5. Security in observe mode first
New WAF and bot rules start by observing. Where the plan supports a log action, rules run in log mode; otherwise they go live on a low-risk subset first. I review real events for a few days, write exceptions for legitimate traffic (payment callbacks, partner integrations, monitoring), and only then switch to block or challenge. Bot rules start on sensitive paths such as login and checkout rather than site-wide.
6. Watch the right signals
After each wave, a scheduled script pulls Cloudflare's GraphQL Analytics API and renders a short report per zone:
- Edge and origin status codes, with 520 to 526 broken out because they point at origin problems rather than client ones.
- Cache hit ratio for paths that should be cacheable.
- Security events by rule, to catch false positives early.
The same data feeds alerts, so a regression in wave 3 is noticed by the team before it is noticed by customers.
What makes it work
Nothing here is exotic. The difference is discipline: an inventory nobody skips, one baseline in code, DNS and proxy moved separately, and a rollback that is fast enough to actually use.