Runbook: migrating DNS hosting to Cloudflare (optional escalation)
Why: Dynadot's nameservers split-brained during an IP change (ns1 served a stale zone for hours — see dns-reliability-overhaul). If that recurs, the durable fix is to host DNS on Cloudflare's anycast nameservers while keeping the domains registered at Dynadot. This is the ready-to-execute runbook — not yet done.
Cost & trade-offs
- Cost: $0 (Cloudflare free plan, full authoritative DNS). Domains stay registered at Dynadot; only the nameservers Dynadot publishes change.
- Pros: anycast nameservers that don't split-brain; fast even propagation; very low TTLs;
ddns-gosupports Cloudflare natively (pipeline + verifier unchanged); free DNSSEC; optional proxy/CDN later (keep "DNS only" to behave like today). - Cons: one more dependency (but far more reliable than what Dynadot just did); one-time nameserver change at Dynadot (~hours to take effect); you must re-create every record before flipping — the classic migration risk.
Current record inventory (what to re-create)
Captured 2026-06-02. No MX/TXT/SPF on any domain (so no email DNS to worry about) — just apex + wildcard:
- skyhouse.dev: apex
A→ current public IP;*CNAME→skyhouse.dev. TTL 120. - botaa.org: apex
A→ current public IP;*CNAME→botaa.org. TTL 120. - room101.com: apex
CNAME-style →cw526dc.glddns.com(GL.iNet router DDNS);*CNAME→room101.com. Self-tracking via the router.
Note the room101 pattern: its apex is an ANAME (alias, RFC-clean apex pointer; Cloudflare calls it CNAME flattening) → cw526dc.glddns.com. The router tracks the IP (verified: cw526dc.glddns.com → current IP, 29 s TTL), and the Dynadot record is static. That sidesteps both ddns-go and the registrar split-brain — confirmed live: while skyhouse.dev (literal-IP A pushed by ddns-go) was still split on ns1, room101.com resolved perfectly. This is the recommended target.
Recommended target architecture (do during the Cloudflare move)
Roll the room101 pattern out to all domains and the IP-update problem largely disappears:
- Each apex → flattened CNAME / ANAME →
cw526dc.glddns.com(the GL.iNet router's DDNS name). The router updates the IP instantly with a ~30 s TTL. *→ CNAME → apex (unchanged) so all subdomains follow.- Retire
ddns-goentirely — the router is now the single IP source. No API token, no in-container config fragility, no literal-IP pushes, and no registrar split-brain (records are static). - Add Cloudflare Email Routing (free) — DNS + email forwarding simultaneously, which Dynadot couldn't do.
- Keep
ip-watch+ip-recovery-verifyfor the Telegram heads-up/✅ confirmation (they don't depend on ddns-go).
Trade-off to weigh: this leans on GL.iNet's glddns.com service as the IP source of truth (vs ddns-go + Dynadot/Cloudflare API). It's the router's native feature and currently fast + accurate, but it's a GL.iNet dependency — if their DDNS hostname ever changes or lapses, the apexes must be re-pointed. Net: fewer moving parts and no split-brain, in exchange for trusting the router's DDNS.
Migration steps
- Cloudflare account → Add Site →
skyhouse.dev(Free plan). It auto-scans existing records; verify against the inventory above and add anything missing (apex A +*CNAME). Set records to DNS only (grey cloud) to behave exactly like today. Set TTL low (Auto/60). - Repeat Add Site for botaa.org (and room101.com if consolidating).
- Cloudflare gives you two nameservers (e.g.
x.ns.cloudflare.com). At Dynadot → each domain → Nameservers → switch from Dynadot DNS to those Cloudflare NS. - Wait for the registry to update (minutes–hours). Confirm:
dig +short NS skyhouse.devshows the Cloudflare NS. - Reconfigure ddns-go: in its UI (
:9876), change the DNS provider from Dynadot to Cloudflare, paste a Cloudflare API token (scoped: Zone.DNS:Edit for these zones), keep domains skyhouse.dev + botaa.org, TTL 120/Auto. (Remember: ddns-go config is in-container with no bind-mount — give it a volume during this change so the token survives a recreate.) - Verify end-to-end: force an update in ddns-go; confirm Cloudflare shows the current IP;
dig @1.1.1.1+@8.8.8.8agree; external check (check-host.net) returns 200; subdomains resolve via the wildcard.
Rollback
Fully reversible: switch the nameservers at Dynadot back to ns1/ns2.dyna-ns.net (the Dynadot-hosted zone still exists unless deleted). Keep the Dynadot DNS records intact until Cloudflare is proven for a week.