BitVPS
Migrating to a new VPS without downtime: the cutover, the DNS and the rollback
Migration playbook

Migrating to a new VPS without downtime: the cutover, the DNS and the rollback

Almost every migration plan budgets its time for the wrong thing. Copying the data is the part people rehearse, and it is the part that rarely fails. The outage comes from somewhere else: a resolver still handing out the old address a day after you changed the record, a database that quietly accepted twenty minutes of writes after you took the dump, a certificate that only ever existed on the disk you are about to destroy, a payment gateway that allowlisted an IP nobody wrote down. None of those are hard problems. They are all scheduling problems, and they are all solved by doing things in an order that leaves you somewhere to retreat to. This guide is that order — rehearse on the new machine while the old one still serves, cut over on a written runbook, and keep a rollback that has an expiry date instead of a hope.

No KYC, ever DMCA ignored No traffic logs Live in 60 seconds

The copy is the easy part — what actually causes the downtime

Ask someone how long their migration will take and they will estimate the transfer. Two hundred gigabytes over a gigabit link, call it forty minutes, add an hour for slack. That number is usually right and almost never the number that matters, because the transfer happens while the old server is still answering requests. Nobody is down during the copy. They are down during the interval between the new machine is correct and the internet agrees, and the length of that interval is set by caches you do not control.

Four failure modes account for most real migration outages, and none of them is bandwidth. A DNS record whose time-to-live was still a day when you changed it, so a meaningful share of the world keeps connecting to a machine you already turned off. A database that took writes after you dumped it, so the new host starts life with a quietly stale copy and you discover which twenty minutes are missing from a customer complaint. A TLS certificate and its ACME account key that existed only on the old disk, so the new host serves a name mismatch to every browser. And an outbound integration — a payment processor, a bank SFTP drop, a partner API, a database at a third provider — that was allowlisted by IP address, which fails silently at the worst possible remove from the change that caused it.

It is worth being blunt about the phrase everyone uses. "Zero-downtime migration" almost always means a few seconds of refused connections that no human noticed, and that is a perfectly good outcome to aim for. Genuinely zero requires both machines serving correctly at the same time, which requires an application that tolerates being two machines: no local session state, no local upload directory that only one side has, and a database that either replicates or lives somewhere else entirely. Most self-hosted stacks do not meet that bar, and the honest plan is a short, controlled, rehearsed freeze rather than an architecture project you did not intend to start.

So the shape of a good migration is not clever. Build the new machine early and prove it works while the old one carries the load. Shrink the irreversible part to a few minutes of scripted commands. Change one thing at a time. And keep the ability to go back, with a date on which you stop paying for it.

Inventory: the parts of a server that are not in your repository

Your deployment repository describes the application. It does not describe the machine. Somewhere between the first apt install and today, a server accumulates a layer that no version control has ever seen: packages installed by hand to fix one problem, a systemd unit someone wrote in a hurry, a cron job under a service account, firewall rules, sysctl tuning, a locale, a timezone, a swap file, TLS certificates, SSH host keys, database roles and grants, and a media directory that is not in the repository precisely because users put it there. Migrating means reproducing that layer deliberately, and the only way to do it deliberately is to write it down first.

Five commands capture most of it. dpkg --get-selections on Debian and Ubuntu, or dnf repoquery --userinstalled on Rocky and Alma, gives you the package set. systemctl list-unit-files --state=enabled gives you everything configured to start at boot, which is a better question than "what is running" because it includes the thing that is currently crashed. crontab -l -u for each account in /etc/passwd, plus the contents of /etc/cron.d and every .timer unit, gives you the scheduled work. nft list ruleset or ufw status numbered gives you the firewall. And ss -tulpn gives you what is actually listening, which is how you discover the service you forgot exists. Write all five to files and copy them off the machine, not onto it.

Then there is the inventory that nobody writes down, which is the one that bites weeks later: everything, anywhere, that trusts your current IP address. Payment gateways with IP allowlists. A managed database at another provider whose access rules name the old address. An SMTP relay that authenticates by network. A monitoring service, a webhook sender that only posts to known addresses, a colleague's firewall. Grep your own configuration and documentation for the old address, then check the control panel of every third-party service you pay for. These break after the cutover, asynchronously, at whatever moment the integration next runs — which is why they are so often diagnosed as "something else went wrong at the same time".

Finally, capture the DNS zone itself. Export it, or at minimum record every record type, value and TTL, because a migration is exactly the moment when someone notices a record that has been wrong for two years and helpfully fixes it in the middle of everything else.

What to inventoryWhere it livesWhat breaks if you forget it
Hand-installed packagesPackage databaseA service refuses to start on the new host for a missing library nobody remembers adding.
Enabled units and timerssystemdBackground work silently stops: no backups, no certificate renewal, no queue worker.
Cron jobs per userPer-account crontabsNightly jobs vanish and are noticed at the end of the month.
Firewall rulesetnftables or ufwEither a port stays closed and the app looks broken, or everything is open.
TLS certs and ACME account keyCertificate directoryBrowsers show a name mismatch, and renewals start a fresh account.
Database roles and grantsServer globals, not the dumpThe data restores fine and the application cannot log in to it.
Third-party IP allowlistsOther people's panelsPayments, webhooks and partner APIs fail hours or days later.
DNS zone with TTLsYour DNS providerYou cannot tell which records still need to move, or how long they are cached.

Lower the DNS TTL first — and understand why it has to be days early

A recursive resolver caches an answer for the time-to-live it was handed, and it is under no obligation to ask again before that expires. This has a consequence that trips up almost everyone the first time: lowering a record's TTL from 86400 to 300 does nothing whatsoever for a resolver that cached the old answer ten minutes ago. That resolver holds the day-long answer for another twenty-three hours and fifty minutes, and only then learns about your new five-minute TTL. The low TTL therefore has to be published at least one full old TTL before you intend to move anything. If your records are on a one-day TTL, the first action of the migration happens two days before the cutover, and it is a one-line change.

Do it for every record that points at the machine, not just the one you have in mind. The A record and the AAAA record — an IPv6 address left pointing at the old host is a beautifully confusing bug, because dual-stack clients will silently prefer it and only some of your users will be affected. The MX records. Any CNAME in front of them, remembering that a client following a chain is subject to the TTL of every link in it, so a five-minute A record behind a day-long CNAME is still effectively a day. And if you are also changing DNS providers, do not do that in the same week: nameserver delegation and glue records are cached by the parent zone on their own schedule, and combining the two changes means that when something breaks you will not know which one broke it.

Verify rather than assume. dig +noall +answer example.com A shows you what a resolver currently holds and the countdown remaining on it; querying your authoritative server directly with dig @ns1.example.net example.com A shows what you actually published. If the two disagree, you are looking at the cache, and the number next to the record is exactly how long you have to wait.

Two nuances are worth budgeting for. Some resolvers clamp: a fraction of ISP and enterprise forwarders enforce a minimum TTL regardless of what you publish, which is technically discouraged but entirely real. And under RFC 8767 a resolver may deliberately serve a stale answer when it cannot reach your authoritative servers — excellent behaviour for resilience, inconvenient the day you want a change to propagate. Between them, plan for a long tail measured in hours, occasionally a day, after the bulk of traffic has moved. The tail is not a problem as long as the old machine is still answering correctly, which is the entire reason it stays up. RFC 2181 is the reference for how TTLs are supposed to behave when implementations disagree.

WhenActionWhy then
T minus 7 daysInventory the old server; write the runbookEverything found here changes the plan, so it has to happen before the plan is fixed.
T minus 3 daysProvision the new VPS; first restore; rehearseLeaves time to rebuild the machine a second time when the rehearsal finds gaps.
T minus 2 daysDrop A, AAAA and MX TTLs to 300One full old TTL must elapse before the low value is in every cache.
T minus 1 dayFirst rsync pass; start database replicationLong and unattended, with the old server still carrying production traffic.
T zeroFreeze writes, final sync, verify, flip DNSThe only irreversible window; it is short because everything above already happened.
T plus 1 hourWatch both access logsThe decay of traffic on the old host is your real measurement of cache expiry.
T plus 7 daysDecommission the old host; raise TTLs backRollback has expired; a permanent 300-second TTL costs latency for no benefit.

Rehearse on the new box while the old one is still serving

The single highest-value habit in this entire process is that the new machine exists and works days before it matters. A VPS is a rental by the hour in practical terms: on our plans a server is live roughly sixty seconds after payment confirms, there is no setup fee and no contract, so a three-day rehearsal on a $8.50 tier costs about a dollar. That is a remarkably cheap way to convert every unknown in your migration into a known before the clock is running.

Test it by hostname, not by IP. Browsing to the new server's address gives you the default virtual host, no SNI match, a certificate warning and a route set that may not resemble production at all — and it will happily convince you that something works when it does not. The right tool is a resolver override. curl --resolve pins one hostname to one address for a single command, so curl --resolve example.com:443:203.0.113.10 https://example.com/ exercises the real name, the real TLS handshake and the real virtual host against the new machine while the rest of the world still reaches the old one. For interactive testing, one line in your workstation's hosts file does the same job for a browser.

What the rehearsal reliably catches is the accumulated undocumented layer from the previous section. A daemon that has been running since before the last reboot and is not actually enabled. A cron job that depends on a local mail transport to report failures. File ownership that survived the copy as numbers but means something different on a machine with a different /etc/passwd. A hardcoded address in a configuration file. A database that was created with one character set on a version that defaulted differently. A Python or PHP minor version that deprecated something you use. Each of these costs ten minutes to fix during a rehearsal and an hour to diagnose during a cutover.

One test is worth more than the rest: build the new machine twice. Rebuild it from scratch using only your inventory and your backup, without looking at the old server. If it comes up correct the second time, your inventory is real and your runbook is executable. If it does not, you have just found the gap in a situation where finding it costs nothing. Take a snapshot once it is right — hourly snapshots with seven-day retention are included on every plan, and a restore is roughly thirty seconds — so that any later experiment on the new box is reversible too.

rsync twice: a long pass while it runs, a short one in the window

The two-pass pattern is what keeps the maintenance window short. The first pass runs with the service fully live and can take as long as it takes — hours, overnight, across a weekend. The second pass runs during the freeze and transfers only what changed since the first, which on a normal workload is a small fraction of the data and a predictable number of minutes. You are trading a long unattended copy for a short attended one, and the trade is almost always worth it.

The flags matter more than people expect. rsync -aHAX --numeric-ids is the baseline: -a for archive mode, -H to preserve hardlinks — which matters enormously for Maildir stores and for deduplicated backup trees, where dropping them can multiply your disk usage — -A for POSIX ACLs, -X for extended attributes such as SELinux labels and file capabilities, and --numeric-ids so that a user ID is copied as a number instead of being resolved through the source's user database and re-resolved through a different one on the destination. Add --info=progress2 for a single sane progress line and --partial so an interrupted transfer resumes instead of restarting. Leave -z off unless the link is genuinely slow: compressing already-compressed media and encrypted archives just moves the bottleneck to the CPU.

Use --delete on the second pass only. On the first pass it is a footgun with no upside, because there is nothing on the destination that needs removing. On the second pass it is essential: without it, a file you deleted on the old server between the two passes lives on forever on the new one, which is how a migration ends up serving content that was taken down deliberately.

What you should not do is rsync the whole root filesystem. It is a popular shortcut and it produces a machine nobody can reason about. Even with /proc, /sys, /dev, /run and /tmp excluded, you end up with a system whose kernel and initramfs came from one host, whose network interface naming was derived from different hardware, whose /etc/machine-id is now duplicated across two live machines, and whose accumulated five years of configuration drift you have just paid to preserve. Migrate the data and the configuration you inventoried; install the operating system fresh from a template. It takes an extra hour, it gives you a clean modern base, and it means the new server is a machine you understand rather than a copy of one you did not.

Verify the copy rather than trusting the exit code. After both passes, run the same command again with -n --checksum --itemize-changes: a dry run that compares content rather than timestamps and prints one line per difference. Silence is the result you want, and it is a far stronger statement than du -s matching on both sides.

The database is the part rsync cannot help you with

Copying a live database directory at the file level gives you a torn copy: some pages written, some not, no guarantee that any of it forms a consistent transaction boundary. InnoDB may well recover from it and appear fine, which is worse than failing, because you will only find out on the day the recovery does not work. There are exactly two honest strategies — take a transactionally consistent dump, or replicate — and the choice between them is a choice about how much downtime you are buying.

For the dump route on MySQL and MariaDB, mysqldump --single-transaction --routines --triggers --events gives a consistent snapshot without locking the whole server, with two caveats worth knowing: it is consistent only for transactional tables, so a single legacy MyISAM table quietly breaks the guarantee, and any DDL statement executed during the dump breaks it as well. On PostgreSQL, pg_dump -Fc handles each database, and pg_dumpall --globals-only handles the half that people forget — roles, passwords, grants and tablespaces live at the cluster level, not inside any single database, so a restore without them produces perfect data that the application cannot authenticate to.

The number that decides your window is not the dump size, it is dump plus transfer plus restore plus index rebuild, and the last of those is usually the largest. Measure it during the rehearsal on the real dataset instead of estimating it. A few gigabytes is minutes; a hundred gigabytes with substantial indexing can be hours, at which point the dump route stops being viable for a live service and replication is the answer rather than an optimisation.

The replication route inverts the cost: you do the work in advance and the cutover becomes trivial. Set up a replica on the new host days ahead — GTID-based replication for MySQL and MariaDB, pg_basebackup and a streaming standby for PostgreSQL, or logical replication when you are also crossing a major version. Let it catch up and stay caught up. The cutover is then: stop writes on the primary, wait for the replica to report zero lag, promote it, point the application at it. That is seconds, and it is the only approach that honestly deserves the phrase near-zero downtime.

Do not forget the state that is not in the database. Redis, Valkey and Memcached hold sessions, rate-limit counters and queues; a search index in Elasticsearch, OpenSearch or Meilisearch holds a derived copy of your data. For each one, decide explicitly whether to migrate it or rebuild it. A Redis instance holding sessions is worth moving with a BGSAVE and a copy of the resulting file, unless you are content to log everybody out. A search index is almost always faster to reindex on the new host than to transfer — but reindexing takes time, so start it during the rehearsal and not during the freeze.

StrategyCutover downtimeWork before the windowWhen it is the right call
File copy of a running data directoryNone, and the data may be corruptNoneNever. Included here because it is the thing people try first.
Consistent dump and restoreDump plus transfer plus restore plus reindexMeasure it once during the rehearsalDatasets small enough that the measured total fits your tolerance.
Dump, restore, then replay the deltaMinutesBinlog or WAL retention configured in advanceMid-sized datasets where a full replica is more setup than you want.
Replica promoted at cutoverSecondsReplication running and lag-free for daysAnything large, and anything where a long freeze is unacceptable.
Stop the database, copy files coldLength of the copyNoneSmall internal services where an hour offline genuinely costs nothing.

TLS certificates, SSH host keys and the identities you cannot just reissue

Certificate issuance has a chicken-and-egg problem at exactly the wrong moment. The HTTP-01 challenge proves control of a name by fetching a file over the name, which requires the name to already resolve to the machine being validated — and before the cutover, it does not. There are three ways out and you should pick one deliberately. Copy the certificate directory across, which works immediately and is entirely legitimate because it is your key. Use the DNS-01 challenge instead, which validates by publishing a TXT record and therefore works on a machine that nothing yet points at, and which is the only option for a wildcard certificate anyway. Or accept a short window after the DNS flip during which the new host requests its own certificate, which is fine for a personal site and not fine for anything with users.

If you copy, copy the whole thing. The certificates and private keys are the obvious part; the ACME account key is the part that gets left behind, and without it the client on the new host registers a brand-new account at the first renewal. Everything appears to work, and you have silently lost the account's history and any account-scoped state. Preserve ownership and permissions as well — private keys at 600, the archive directory at 700 — because a copy that widens permissions on a key is a worse outcome than the migration you were worried about.

Do not plan to reissue repeatedly and see what happens. Let's Encrypt rate limits cap duplicate certificates for an identical set of hostnames per week, and two failed rehearsals plus a real issuance is a surprisingly easy way to reach the ceiling on the one day you cannot afford to wait. Point rehearsals at the staging environment; save the production endpoint for the real thing.

SSH host keys are the other identity in play, and here the right answer genuinely depends on why you are moving. Copying /etc/ssh/ssh_host_* makes the migration invisible: every client's known_hosts still matches, and no automation stops with a fingerprint warning. That is convenient, and it is the correct choice for a routine move between providers you trust. It is the wrong choice if the reason you are leaving is that you no longer trust the old host or the old provider — anyone with access to that disk has had your host private key, and carrying it to the new server carries the problem with it. In that case generate fresh keys, publish the new fingerprint through a channel that is not the server, and have clients run ssh-keygen -R against the old entry. See the OpenSSH sshd manual for where the keys live and how they are selected.

One rule is absolute either way: never run both machines simultaneously with the same host key and the same hostname resolving to both. That is a configuration in which a client cannot tell which server it reached, which is precisely the property host keys exist to prevent. During the overlap window, the old host keeps its identity and the new one has its own, or the old host's name is retired the moment the new one adopts its key.

The cutover: the ten minutes that need a written order

Everything so far was reversible and unhurried. The cutover is neither, which is the whole argument for writing it down beforehand: improvisation under time pressure is what turns a five-minute window into a two-hour incident. The runbook should be literal commands in order, with the expected output next to each one, so that executing it requires reading rather than thinking.

The order that works. First, stop writes on the old host — a maintenance page, a read-only flag, or simply stopping the application while the web server still answers with a holding page. Second, run the final rsync pass with --delete. Third, finish the database step: either the dump-transfer-restore you measured, or stop writes on the primary, confirm zero replication lag and promote the replica. Fourth, start the services on the new host and give them a moment to settle — connection pools, queue workers and certificate loading all take longer on a cold start than you remember. Fifth, verify. Sixth, and only after verification passes, change the DNS records. Seventh, leave the old host running.

Verification has to mean something. Fetching the homepage proves the web server is up and nothing else. Use curl --resolve against the real hostname on a path that touches the database and returns something you can assert on — a health endpoint that checks database connectivity, cache connectivity and free disk is worth writing for this reason alone. Then read the new host's access log and confirm your request is in it. That last step sounds pedantic and it is the step that catches the case where your resolver override did not take effect and you have just carefully verified the old server.

After the DNS flip, watch both access logs side by side. Traffic on the new host climbs; traffic on the old host decays as caches expire, and that decay curve is the only honest measurement of how long the tail actually is. When the old host's log flattens near zero, the migration is finished. Until it does, the old machine has to keep behaving correctly for the users still reaching it — which is why it stays powered and read-only rather than being switched off in a moment of enthusiasm. If the application cannot be made read-only, the old host serving stale data for a few hours is usually a smaller harm than the old host serving connection refused, but that is a judgement to make in advance rather than at the time.

Change one thing. Not the operating system major version and the host. Not the DNS provider and the records. Not the PHP version and the machine. Each simultaneous change multiplies the hypotheses you will have to test when something misbehaves at three in the morning, and the upgrade you were tempted to fold in will still be there next week, on a machine you can now roll back to.

Mail, rDNS and the reputation that does not travel with you

If the server sends mail, the migration has a second half that has nothing to do with the migration. An IP address carries a sending reputation, that reputation is built over weeks, and it does not come with you. This is the single most common way a technically flawless migration produces a week of user complaints, and the mitigations all have to start before the cutover rather than after.

The record that breaks first is SPF. A policy containing an ip4: mechanism naming the old address starts failing the instant you send from the new one, and because RFC 7208 policies are DNS records they have their own TTL to plan around. Add the new address to SPF before the cutover, keep both listed during the overlap, and remove the old one when you decommission. DKIM travels fine as long as you copy the private key with the rest of the configuration, since the public half is already published. DMARC needs no change, but it is worth confirming the reporting address still exists on a machine that is about to be destroyed.

Reverse DNS is not optional for a sending host. The PTR record on the new address has to exist and has to match the name the server announces in its HELO or EHLO, or a large share of receivers will reject on that alone. On our plans PTR is editable from the panel, so it is a checkbox rather than a ticket — set it during the rehearsal, verify it resolves, and confirm the forward record matches back. Then check the new address against the major blocklists before you send anything at all: recycled addresses occasionally arrive already listed, and finding that out from your own test message costs nothing while finding it out from a customer costs a lot. Spamhaus is the one that matters most.

For receiving, keep both hosts reachable. Add the new server as the priority-10 MX and leave the old one at priority 20 for a week rather than deleting it: senders retry, some of them cached your MX record, and a mail server that quietly accepts on the old host is far better than a bounce. Then, before you destroy the old machine, flush and drain its queue — postqueue -f to force a delivery attempt and mailq to confirm the result is empty. Messages sitting in a queue on a server you are about to delete are not delayed. They are gone. The self-hosted mail server guide covers the deliverability side in full.

When the old provider has already switched the machine off

Not every migration is planned. A suspension after an abuse complaint, a card that expired, an account locked for a reason nobody will explain — the common thread is that you are now migrating from a machine you cannot log in to, and the comfortable order of operations above no longer applies. It is worth knowing in advance what is usually still possible, because the window in which it is possible is often short.

While an instance is suspended but not yet deleted, three avenues frequently remain, in decreasing order of usefulness. The control panel may still offer a snapshot or backup download, which is the whole problem solved in one step. A rescue or recovery mode may still boot a live image with your disk attached, letting you mount the filesystem and copy what matters out over the network. And the emergency console may still attach even when networking is disabled, which is enough to read configuration files, recover a key, or note down what was installed — but it is not a path for a database, so do not plan around it. Try them in that order, and try them immediately: retention after suspension is a policy, not a right, and it is frequently measured in days.

There is a limit that no support ticket resolves, and it is worth stating plainly because it is a direct consequence of the model this site sells. On a host that never collected an identity — including this one — there is no way to prove you are the account holder other than holding the account credentials. That is the point of no-KYC hosting and it is also its bill: nobody can restore your access on the strength of a passport photograph, because no passport was ever attached to anything. The no-KYC hosting guide covers the tradeoff in full; the operational consequence is simply that your recovery plan cannot route through anyone else's goodwill.

The emergency sequence is the planned one with the overlap removed. Bring the new host up from your off-machine backup. Verify it properly by hostname, because the temptation to skip verification is strongest exactly when you are already down. Then move DNS immediately — the TTL discipline still applies, except that you are now paying the cache tail as outage instead of spending it as overlap, which is the single clearest argument for keeping TTLs modest as a standing policy rather than as a migration step. Expect degraded mail for a while and warn people early rather than explaining later.

The lesson generalises past this one bad day. A backup that lives on the server is not a backup; it is a second copy of the thing you are about to lose. The property that matters is that the copy is somewhere else, encrypted before it leaves, append-only so that a compromised or suspended host cannot delete its own history, and — the part everyone skips — actually restored once, into a throwaway instance, so you know the restore works. The hardening checklist covers setting that up on a new machine; if you are reading this section because it is already too late, set it up on the machine you are migrating to, today, before anything else.

Rollback: define the trigger, and give it an expiry date

Almost everyone says they have a rollback plan and almost nobody has written down what would trigger it. Without a trigger, the decision gets made at the worst moment by whoever is most tired, and the usual outcome is that nobody rolls back because it always feels like the current problem is five minutes from being fixed. Decide in advance: error rate above some threshold for some duration, a specific function broken, or simply a wall-clock time after which you stop debugging and go back.

The constraint that determines what rollback even means is writes. From the moment the new host accepts its first write, going back means either discarding those writes or replaying them onto the old machine. So you are really choosing between two different products. A rollback taken before the first write is a DNS change and takes minutes. A rollback taken after a day of production traffic is a migration in the opposite direction, with all the same steps, and it takes as long as this guide describes. Know which one you have at any given moment, and note that the second kind is much easier if you kept the old machine's database able to receive a restore rather than repurposing the box.

Keep the old server for about a week: powered, read-only, DNS TTL still low, monitoring still pointed at it. On the smallest plan that insurance costs roughly two dollars, which is not a number that should feature in the decision. What should feature is that a week is long enough for the weekly cron jobs, the monthly-ish reports and the integration that only fires on Fridays to have run at least once on the new host — those are the failures that a two-day window misses entirely.

Then decommission on purpose. Confirm the old host's access log has been flat for a day. Take one final backup of it and keep that longer than you think you need to. Revoke everything it held: API keys, deploy keys, database users, its entries in other people's IP allowlists, its monitoring checks, its SPF entry, its MX record. Destroy the instance. And finally put the DNS TTLs back to something sensible — a permanent 300-second TTL means resolvers revalidate twenty times an hour for the rest of the site's life, which buys you nothing once the migration is over and costs a little latency on every cold lookup. An hour is a reasonable standing value; a day is fine for records you never expect to change quickly.

The migration, in the order to run it

A week out, inventory the old machine and write the runbook: packages, enabled units, crontabs, firewall, listening sockets, database roles, and every third party that allowlisted the old address. Three days out, provision the new VPS, restore onto it, and rehearse the whole thing by hostname with a resolver override — then rebuild it once from the inventory alone to prove the inventory is real. Two days out, drop the A, AAAA and MX TTLs to 300 so that a full old TTL elapses before you need them low.

One day out, run the first rsync pass with -aHAX --numeric-ids while production carries on, and bring up database replication or measure the dump window on the real dataset. Sort out certificates in the same pass — copy the certificate directory including the ACME account key, or switch to DNS-01 — and set the PTR record and the new SPF entry on the new address before anything sends mail from it.

At the window: freeze writes, final rsync with --delete, finish the database, start services, verify against the real hostname on a path that touches the database, confirm the request landed in the new host's log, and only then flip DNS. Leave the old host powered and read-only. Watch both access logs until the old one flattens.

A week later: confirm the weekly jobs have run on the new host, take a final backup of the old one, revoke its keys and allowlist entries everywhere, destroy it, and raise the TTLs back. If you want the new machine to be better than the one you left rather than just newer, the first fifteen minutes of hardening is the natural next step, and it is much easier to do on a server that has not yet accumulated its own undocumented layer. If you are still choosing where to land, the jurisdiction guide covers what actually differs between Iceland, the Netherlands, Romania and Switzerland — and you can have the target machine running in about a minute to start the rehearsal today.

Quick answers

Frequently asked

How much downtime does a VPS migration actually take?
For a small site with a database that dumps in a couple of minutes, a rehearsed cutover is typically two to ten minutes of frozen writes, followed by a tail of hours during which some resolvers still send traffic to the old machine. With a database replica promoted instead of dumped, the frozen window is seconds. The variable that dominates is not your bandwidth, it is whether you lowered the DNS TTL far enough in advance and whether you kept the old server answering during the tail.
Do I really have to lower the DNS TTL first, or can I just change the record?
You can change the record at any time, but resolvers that already cached the old answer will keep using it for the remainder of the TTL they were given — which on a default one-day record means up to twenty-four hours of traffic still arriving at the old machine. Lowering the TTL only helps if it is published at least one full old TTL before the change, so it is the first action of the migration, not the last. If you cannot wait, the change still works; you simply pay the cache tail as a longer overlap, which is fine as long as the old host stays up.
Can I just rsync the whole root filesystem to the new VPS?
You can, and it usually boots, and it is still the wrong approach. You end up with a machine whose kernel and initramfs came from different hardware, whose interface naming may not match, whose /etc/machine-id is now shared with a live server, and which carries every piece of configuration drift the old box accumulated. Install the OS fresh from a template and rsync the data and the configuration you inventoried. The extra hour buys you a server you can actually reason about.
How do I move a MySQL or PostgreSQL database without losing writes?
Two options. Take a consistent dump — mysqldump --single-transaction for InnoDB, or pg_dump plus pg_dumpall --globals-only for the roles and grants that are not inside any database — and accept a freeze lasting as long as dump, transfer, restore and reindex take. Or set up a replica on the new host days in advance, then stop writes, wait for zero lag and promote it, which reduces the freeze to seconds. Never copy a running data directory at the file level: the result is a torn snapshot that may appear to work.
Should I copy my SSH host keys and TLS certificates to the new server?
Certificates, yes — copy the whole directory including the ACME account key, keeping private keys at 600, or switch to the DNS-01 challenge so the new host can issue before DNS points at it. Host keys are a judgement call: copying them makes the move invisible to clients and to automation, which is right for a routine migration, but wrong if you are leaving because you no longer trust the old provider, since whoever had that disk has had the key. Whichever you choose, never have both machines live with the same host key and the same hostname resolving to both.
Will my email break when I move to a new IP address?
Deliverability will dip, because reputation belongs to the address rather than to you. Update SPF to include the new address before you cut over and keep both listed during the overlap, copy the DKIM private key so the published public key still validates, set the PTR record on the new address to match your HELO name, and check the address against the major blocklists before sending anything. Keep the old host as a lower-priority MX for about a week so retries and cached lookups still land somewhere, and drain its queue before you destroy it.
My old provider suspended the server before I had a backup — is anything recoverable?
Sometimes, and the window is short. While the instance is suspended rather than deleted, try in this order: a snapshot or backup download from the panel, a rescue or recovery boot that mounts your disk, and the emergency console, which often attaches even when networking is off and is enough to recover configuration and keys but not a database. What no provider that never collected an identity can do — including us — is verify who you are and hand the account back, because there was never an identity attached to verify against. That is why the backup has to live somewhere other than the server.
How long should I keep the old server, and can I move between jurisdictions later?
Keep it about a week, powered and read-only, so that weekly cron jobs and the integration that only runs on Fridays have each fired at least once on the new host. On the smallest plan that costs a couple of dollars. Moving between jurisdictions later works exactly the same way: our four regions — Iceland, the Netherlands, Romania and Switzerland — carry the same plans at the same prices, so a jurisdiction change is a migration between two of your own servers with no contract to unwind and no fee to pay.
Apply this

Workloads this guide applies to

Each card opens a workload-specific page with sizing recommendations and a sysadmin FAQ.

Keep reading

Other guides

Companion reads that pick up where this one stops.

Read enough? Deploy in 60 seconds

No email verification, no ID, no account. Pick a plan, pay in any cryptocurrency, get root.