Safe Cutover — Skill Shareware×
Address
https://johnarndt.net/skills/safe-cutover

L-03 · John Lab · Build and make · All 26 skills

Moving live sites without breaking them

Snapshot first, prove parity, cut over with a health gate and automatic rollback, and keep the old host warm. The checklist I used to move my sites off Vercel, as a Claude skill.

Where it came from: The checklist from moving my sites off Vercel and onto Cloudflare, plus the guarded release script.

In October I moved most of my sites from Vercel to Cloudflare. The goal was that nobody would notice. That takes a checklist, because every migration outage I've caused or seen came down to one of three things: a DNS record nobody saved, a secret that didn't come across, or a scheduled job that ran in two places or in none.

Snapshot before you touch anything

Before any DNS change, the script saves every record in the zone, plus routes and scheduled jobs on both hosts, to a timestamped file only I can read. Snapshots are never overwritten. If the cutover goes wrong, rollback restores that exact file. I have more of these snapshot files in my home folder than I'd like to admit, and every one of them has been worth keeping.

Prove parity before cutover day

The new build runs on staging, and a parity script requests every important URL from both live and staging and compares status codes, redirects, titles, canonical links and structured data. The target is zero differences, not "a few, but they look fine." It also checks that sessions issued by the old host still work on the new one, so nobody gets logged out mid-move.

The cutover is a script, not a series of clicks

  1. Snapshot.
  2. Preflight, which refuses to run unless the live commit is in the branch being deployed, every secret resolves, a signed webhook test returns 200 and a bad signature returns 400, and parity shows zero differences.
  3. Turn off scheduled jobs on the old host.
  4. Point DNS at the new host, without touching the mail records.
  5. Poll the health endpoint until it reports the new platform at the right commit, or roll back automatically.
  6. Turn on scheduled jobs on the new host.
  7. Freeze the old host and take a second snapshot.

The old host stays warm for a week. Decommissioning needs my explicit OK.

Where it bit me anyway

  • Cron didn't fire. After one cutover, the new host's scheduled jobs just never ran. Nothing failed loudly. I found out because something downstream stopped updating. Now there's a freshness monitor that alerts when a job misses its slot.
  • Some secrets can't be read back. The old host stored one as write-only, so preflight refused to go, which was the right call. The fix was a planned rotation, or a temporary bridge that accepts both old and new sessions.
  • Live moved six times in one day. I was shipping fixes while preparing the cutover. Re-merge the live commit right before you cut.
  • The cache ignored the hostname. www and the bare domain shared cache entries until I added an explicit redirect rule.

Every deploy after that

The same discipline became my normal release. The deploy command refuses a dirty tree, a commit that isn't HEAD, or one that doesn't contain the last live commit. Afterward it checks the version endpoint and rechecks every URL in the sitemap. On the last soxoa.com release, all 118 came back 200.

Steal it

The skill is safe-cutover in my John Lab plugin.

/plugin marketplace add johnarndt/john-lab
/plugin install john-lab@john-lab
Install
/plugin marketplace add johnarndt/john-lab
/plugin install john-lab@john-lab
Download safe-cutover.zipView on GitHub
View SKILL.md
---
name: safe-cutover
description: "Use when a user wants to move a live site or app to a new host, change its DNS, or ship a risky release without downtime. Triggers include 'migrate from Vercel to Cloudflare', 'move my site to a new host', 'switch DNS', 'cut over to the new server', 'deploy this safely', 'can we roll back if it breaks', or 'make the deploy refuse a dirty tree'. Snapshots DNS and config first, proves parity on staging, cuts over with a scripted health gate and automatic rollback, and keeps the old host warm until the owner retires it."
---

# Safe cutover

Most outages during a migration come from one of three things: a record nobody snapshotted, a secret that did not come across, or a scheduled job that runs in two places or in none. This skill makes each of those a checked step.

## Phases

**P0 · Stabilize.** The current host must be healthy before you move anything. Fix it there first.

**P1 · Build for the new host** on its own branch. Do not change behavior; this is a platform move.

**P2 · Prove parity on staging.** A script requests every important URL on both live and staging and compares status codes, redirects, page titles, canonical links and structured data. The target is zero differences. Also confirm sessions issued by the old host still work on the new one, so nobody is logged out.

**P3 · Cut over**, scripted, in this order:

1. Snapshot DNS: every record in the zone, plus routes and scheduled jobs on both hosts, to a timestamped JSON file with owner-only permissions. Never overwrite a snapshot.
2. Preflight, and refuse to continue unless every check passes:
   - the live commit is contained in the branch being deployed (re-merge it right before cutover; live may have moved today);
   - every required secret resolves on the new host;
   - a signed webhook test returns 200 and a bad signature returns 400;
   - the parity check shows zero differences.
3. Disable scheduled jobs on the old host.
4. Point DNS at the new host. Leave mail records (MX, SPF, DKIM, DMARC) untouched.
5. Poll the health endpoint until it reports the new platform at the expected commit. If it does not within the timeout, restore the snapshot automatically.
6. Enable scheduled jobs on the new host, then confirm each one actually fires.
7. Freeze builds on the old host. Take a second snapshot.

**P4 · Decommission**, only with the owner's approval, after a waiting period (default 7 days).

## Guarded release (for every deploy afterwards)

The deploy command refuses a dirty working tree, a commit other than HEAD, or a commit that does not contain the recorded live commit. After deploying it checks the version endpoint and build header for the new commit, then rechecks every sitemap URL for a 200. Record the result in `RELEASE_STATE.md`.

## Rules learned the hard way

- **Scheduled jobs are the silent failure.** After one cutover the new host's cron never fired. Add a freshness check that alerts when a job has not run on time, and keep the old host's jobs available to switch back on.
- **Some secrets cannot be read back** from the old host. Plan a deliberate rotation, or a temporary bridge that accepts both old and new sessions, before cutover day.
- **Caches may ignore the hostname.** Add an explicit www-to-apex (or reverse) redirect rule rather than trusting the cache.
- **Owner does the dashboard clicks; the agent runs the commands and the checks.** DNS changes on a production zone get the owner's go-ahead each time.