Most migrations do not fail during the copy. They fail in the twenty minutes after the DNS change, when half the internet is still talking to the old server and the other half has moved on — and both machines are accepting writes. The files arrived intact, the site loads, and yet orders are landing in two databases that will never be reconciled. Avoiding that has almost nothing to do with transfer speed and almost everything to do with sequence: what you change first, what you freeze, and what you keep running until you are certain.
What no downtime actually means
Worth being precise, because the phrase covers two very different promises and they cost very different amounts of effort. Deciding which one you are buying is the first real decision of the migration.
If you have to sacrifice one, sacrifice availability. A maintenance page for four minutes is a thing you can explain. Divergent data is a thing you cannot fix, because there is no authority left to say which copy was right.
Write down, before you start, the longest write-freeze you can tolerate. Four minutes, thirty seconds, zero. That single number decides everything downstream — whether a dump and restore is enough, whether you need replication, or whether you need a proxy in front of the whole thing. Choosing the tooling before choosing the number is how migrations acquire their surprises.
Take the inventory before you take a copy
A server accumulates things nobody documented: a cron job added during an incident, a firewall rule for a partner's address, an API key pasted into a service file. The copy will not carry them, and you will find them one at a time over the following fortnight. Half an hour of inventory now removes almost all of that.
What listens
Run ss -tulpn and account for every listening socket. Each one is a service that must exist on the new box, and each unexplained port is worth understanding before you replicate it — a migration is a good moment to notice what has been running since 2023.
What runs on a timer
crontab -l for every user including root, systemctl list-timers, and any scheduler inside the application itself. Timers are the single most common thing to survive a migration in duplicate, and duplicates are worse than absences.
What is not on the disk
DNS records, the reverse DNS on your address, firewall rules, API keys held by third parties, webhook targets, and any allowlist elsewhere that names your current IP. None of it lives in the filesystem you are about to copy.
What the app assumes
Hard-coded absolute paths, a hostname in a config file, a database socket location, an address in a bind directive. These are the things that break silently — the service starts, and then does not work.
Write it into a file you keep in version control, not into a terminal scrollback. You will read it three times during the cutover, once at a moment when you are not thinking clearly.
Pick the destination on the things you cannot change later
Specs are adjustable; a few properties are not, and those are worth deciding deliberately while you still have a free choice. Since you are moving anyway, this is the cheapest moment you will ever get to fix a constraint you have been working around.
- Location, because latency to your users and the applicable law are both fixed by it. /locations lists the regions and their round-trip characteristics; pick one that fails independently of whatever you are leaving.
- Legal footing, if the reason for the move is that your current provider forwards complaints faster than it forwards packets. /offshore-hosting sets out what jurisdiction genuinely controls and what it does not.
- Billing identity, because a host that requires documents before it accepts payment has, by construction, a file on you. /no-kyc-vps and /pay-with describe the alternative — a balance topped up in crypto, no card, no name attached to the machine.
- Headroom, because migrating twice is the outcome nobody plans for. /vps lists the tiers, and /guides has the sizing method if the current box has never actually been measured.
- IPv6 and a clean IPv4, since a recycled address can arrive carrying someone else's reputation — check it before it becomes a mail problem.
Lower the DNS TTL first, days before
This is the one preparation step that cannot be rushed at the end, because its effect is gated by a clock you do not control. The TTL on your records tells resolvers how long to cache an answer. If it is set to a day, a resolver that asked an hour ago will keep sending users to the old server for the next twenty-three hours no matter what you publish.
- 1
Read the current TTL
dig +noall +answer yourdomain.com shows the remaining cache time; querying your authoritative nameserver directly with dig @ns1.example.net yourdomain.com shows the configured value. Note it down — it sets the length of your waiting period.
- 2
Drop it to 300 seconds
Lower the TTL on every record that will change: A, AAAA, and any MX or CNAME pointing at the host. Five minutes is short enough to make the cutover comfortable and long enough not to hammer your nameservers.
- 3
Wait out the old TTL, then wait again
The new short TTL only reaches a resolver after the old long one expires. Wait at least one full old-TTL period — a day if it was a day — before treating the low TTL as real. Starting this early costs nothing and buys the entire cutover its margin.
Some resolvers ignore your TTL and cache for their own minimum anyway, and a fraction of clients cache for the lifetime of the process. Plan for a long tail of traffic hitting the old address for hours after a textbook-perfect switch. That is not a reason to skip the TTL work — it is the reason the old server has to stay alive afterwards.
Build the new server, do not clone the old one
The instinct is to image the current disk and restore it elsewhere. It is the wrong move, for the same reason it is wrong in a restore: a block-level clone faithfully reproduces the config drift, the orphaned packages, the half-configured service someone abandoned, and any persistence a previous intruder left behind. It also pins you to the old distribution release.
Build the new machine from a current base image, install the services from a script, and copy only the data. The script is the real deliverable — it is what turns the next migration into an afternoon instead of a fortnight, and what lets you rebuild after an incident without archaeology.
- Provision, update and harden before anything else touches it: keys-only SSH, a default-deny firewall, unattended security updates. /guides has the hour-long version of that checklist.
- Install the same major versions of the runtime and the database as production. A migration is a bad time to also upgrade PostgreSQL 14 to 17 — make one change at a time so that a failure has exactly one explanation.
- Recreate users and groups with the same numeric IDs before copying files, or fix ownership by hand afterwards. Transferring with --numeric-ids keeps the numbers intact; mapping them back to the right names is your job.
- Put the new address into every allowlist that currently names the old one — partner APIs, managed database firewalls, payment gateways, your own monitoring — while both are still valid.
Install the services, then stop and disable them. A web server that starts on boot and answers on the new address will be found by scanners, indexed under the wrong hostname, and — worse — will happily serve a half-populated copy of your site to anyone who resolves early. Nothing on the new box should answer publicly until you decide that it does.
Copy the files in two passes
One transfer of a live filesystem is a snapshot of a moving target. Two passes solve it cleanly: the first is long and runs against a live system, the second is short and runs during the freeze, and only the second one has to be fast.
Run the first pass whenever you like — days early is fine. It carries the bulk: the uploads directory, the mail spool, the container volumes, the years of accumulated media. It will be out of date by the time you cut over, and that is exactly what the second pass is for.
- Use rsync -aAXH --numeric-ids --info=progress2 over SSH: -a for the usual attributes, -A for ACLs, -X for extended attributes, -H for hard links, which matter if anything on the box deduplicates.
- Exclude what should not travel: /proc, /sys, /dev, /run, the temporary directories, the package cache and logs you do not need. Copying kernel interfaces wastes time at best and hangs the transfer at worst.
- Add --delete on the second pass only. On the first it is harmless; on the second it removes files that were deleted on the source since then, which is precisely the drift you are trying to eliminate.
- Copy the system configuration selectively rather than wholesale. You want your web server vhosts, your service units and your application config — not the old machine's filesystem table, network configuration or machine ID.
- Generate a throwaway SSH key for the transfer, authorise it on the destination, and remove it when the migration is done. A migration key that lingers for two years is a credential nobody remembers issuing.
Between passes, verify rather than assume. Running the second pass with --dry-run prints exactly what it would change: a list of a few hundred recent files is healthy, and a list of forty thousand means an exclusion is wrong or something is rewriting timestamps for no reason.
Move the database without losing writes
This is where the freeze budget gets spent, and the right technique is entirely determined by the number you wrote down at the start. All three options below are correct — for different numbers.
Whatever you choose, the old database must stop accepting writes before the new one starts. Not shortly after — before. The overlap where both are writable is the split-brain window, and it is the one failure in this entire guide with no clean recovery: two divergent histories and no way to merge them that does not involve reading rows by hand.
Dump schema and data separately when the dataset is large. Restoring the schema first lets you verify structure, indexes and permissions on the new host early, which turns the data load into a single long operation you can start during the freeze already confident it will land.
Issue the TLS certificate before the switch, not after
The certificate on the new server has to be valid at the instant the first user arrives. Discovering otherwise means every visitor meets a browser interstitial, and if you send HSTS headers — which you should — they cannot click through it. There are two ways to hold a working certificate before DNS points anywhere new.
- Copy the existing one. The whole ACME state directory, or just the certificate and key from wherever your client keeps them. It is valid immediately, because validity has nothing to do with which server holds the file, and renewal resumes normally once DNS has moved.
- Issue a fresh one with a DNS-01 challenge, which proves control of the domain through a TXT record instead of an HTTP request to the current address. That works before the migration, before the cutover, and for wildcards, which HTTP-01 cannot do at all.
- Do not attempt HTTP-01 validation on the new server before the switch. The challenge is fetched over port 80 at the domain name, the domain still resolves to the old machine, and the validation fails every time.
Verify it without touching DNS by resolving the hostname to the new address for a single command: curl -sI --resolve yourdomain.com:443:198.51.100.10 https://yourdomain.com/ — substituting the real address. That one line is the highest-value check in the whole migration. It exercises the real vhost, the real certificate and the real application stack on the new box, and it is how you find the misconfiguration while finding it still costs nothing.
If the server sends mail, start earlier
Mail is the part of a migration that fails days later, quietly, and gets blamed on something else. A new address has no sending reputation and may have inherited someone else's, and every authentication record you publish is tied to the address you are leaving.
- Check the new address against the major blocklists before you commit to it. A recycled IP with history is worth swapping while swapping it is still free.
- Set the reverse DNS record on the new address to your mail hostname. Receiving servers check that forward and reverse resolution agree, and a missing PTR on its own is enough to be filed as spam.
- Add the new address to SPF before the cutover and remove the old one afterwards — both listed during the overlap, so mail authenticates from whichever server actually sent it.
- Copy the DKIM private keys rather than generating new ones, so the selector already published in DNS keeps validating. Regenerating means republishing, and republishing has its own propagation delay.
- Warm the new address gradually if you send at any volume. A server that has sent nothing for its entire existence and then emits ten thousand messages is, to a receiver, indistinguishable from a compromised host.
The cutover
Everything above was preparation so that this part is short, ordered and reversible. Do it at your genuine traffic minimum rather than at 3am out of superstition — check your own analytics. Have the rollback written down before you begin, because the moment you need it is the moment you will least want to be composing it.
- 1
Freeze writes on the old server
Maintenance mode, a read-only database user, or an error on write paths at the reverse proxy. Reads keep being served from the old machine throughout — that is what keeps the site up while the data moves.
- 2
Disable every timer on the old server
Comment out the crontabs, stop the timers, stop the queue workers. From this point the old machine must not process anything, or jobs will run twice: two invoices, two emails, two webhook deliveries.
- 3
Run the final delta
The second file pass with --delete, then the final database dump or the replica catch-up. This is the short one, minutes at most, because the first pass already moved the volume.
- 4
Verify the new server against its real hostname
Using the --resolve trick above, exercise a page that reads from the database, a login, and one write path. Confirm the row counts match the source. Do all of this before DNS changes, while rolling back still costs nothing at all.
- 5
Switch DNS and start the services
Update A and AAAA to the new address, then enable and start the application and its timers on the new server. Traffic begins arriving within one TTL and keeps shifting over the following hour.
- 6
Keep the old server serving reads
Leave it up, read-only, for at least twenty-four hours. Clients with stale caches will still land on it, and a read-only old server returns slightly stale pages instead of a refused connection — the difference between an invisible migration and a visible one.
The overlap is also where the cleanest trick lives. Instead of a hard DNS switch, reconfigure the old server as a reverse proxy to the new one at the end of step five. Every straggler that still resolves to the old address is forwarded transparently, the cutover stops depending on DNS propagation entirely, and you dismantle the proxy whenever traffic to it reaches zero. Pass X-Forwarded-For so that your logs and rate limits still see real client addresses.
The first 48 hours
The migration is not finished when the site loads. It is finished when nothing still depends on the old machine, and finding those dependencies is an active exercise rather than a waiting game.
- Watch the old server's access log. Every request still arriving there is an unmigrated dependency — a hard-coded address in a partner integration, a mobile client with a stale cache, a monitoring probe nobody owns. The log is the to-do list.
- Confirm the timers actually ran on the new host. Not that they are enabled — that they ran, at the expected time, with the expected output. A cron job that silently does nothing looks exactly like a cron job that works.
- Test certificate renewal for real with a dry run, rather than discovering in sixty days that your ACME client has been renewing against a server that no longer receives the challenge.
- Check mail flow end to end in both directions, including whatever the application sends automatically. Password resets are the classic casualty, because nobody tests them until a user needs one.
- Take a backup of the new server and restore it somewhere. A fresh machine with no verified backup is a worse position than the one you left; /guides has the full version of that argument.
- Restore the DNS TTL to its normal value once you are confident. Leaving it at 300 seconds forever is a small permanent cost in queries and latency for no remaining benefit.
Decommissioning the old server
The last step is the one that gets skipped, and it is the only one with a privacy consequence. The old disk holds your keys, your database, your customer data and your logs, and cancelling the service does not erase any of it — it releases the volume back into a pool where the next tenant receives whatever the provider's wipe policy left behind.
- Wait for the old access log to go quiet before you touch anything. Cancelling while a payment provider is still posting webhooks to the old address is how a migration becomes an incident a week later.
- Rotate rather than merely delete: every credential the old server held — API keys, database passwords, deploy keys, the temporary migration key. Assume anything that lived on a machine you no longer control is compromised, because eventually it is.
- Overwrite the data before releasing the volume. Shred the sensitive directories, or fill the free space with one large random file and delete it. Imperfect on a virtualised disk, and vastly better than nothing.
- Take a final archive of anything you may want for reference — logs, configuration, the shell history that documents what was actually done — and store it encrypted somewhere that is neither server.
- Cancel the service only after the new one has survived a full billing cycle and a full backup-and-restore test. That overlap month is the cheapest insurance in the entire process.
A timeline you can copy
Nothing here is difficult in isolation. The reason migrations go badly is that the steps get compressed into one evening, where the TTL has not expired, the certificate has not been tested and the rollback has not been written. Spread over a week, each day is twenty minutes of work.
How migrations actually go wrong
Not through the copy. Through the things that were never on the disk, and the things that ran twice.
- Both servers writing at once — the split-brain window, and the only failure here with no clean fix. It is prevented by ordering, not by tooling: stop the old, then start the new.
- Timers running on both machines, which is how customers receive two of everything. Disable the old ones before the final sync, not after.
- A TTL that was never lowered, turning a five-minute cutover into a day-long tail nobody planned to staff.
- File ownership landing wrong because the numeric IDs differ between the machines, so the application starts and then cannot write to its own upload directory.
- A certificate that was going to be sorted out after the switch, meeting an HSTS header that leaves visitors with no way to proceed.
- An address hard-coded somewhere you do not control — a partner integration, a firewall rule, a DNS record for a subdomain you had forgotten existed.
- Cancelling the old server on the day of the move, which removes the rollback path at precisely the moment the statistics say you are most likely to need it.
How long does a VPS migration take?
The work is typically a few hours spread over a week, and the user-visible part is minutes. The bulk transfer and the TTL reduction happen days in advance while everything stays live; the cutover itself is a final delta sync, a DNS change and a verification pass. A site with a few gigabytes of data and a modest database is comfortably a thirty-minute cutover with a freeze measured in single-digit minutes.
Can I migrate with no downtime at all?
Yes, for reads — the old server keeps answering until DNS moves, and turning it into a reverse proxy afterwards removes even the propagation tail. Writes are the harder case: a short freeze is by far the simplest way to guarantee that nothing is lost, and replication shortens that freeze to seconds. A true zero-freeze on writes requires the application to write to both databases during the overlap, which is achievable but adds a failure mode most sites do not need.
Should I clone the disk or rebuild the server?
Rebuild. A clone carries the config drift, the orphaned packages and any persistence left behind by a past compromise, and it pins you to the old distribution release. Installing from a script and copying only the data gives you a clean machine plus a repeatable recipe — which is also what makes the next rebuild fast.
What happens to my IP address and my search rankings?
The address changes; the domain does not, and links, rankings and history follow the domain. Keep the URLs identical, keep the old server answering during the overlap so that no crawler ever sees a connection error, and the change is effectively invisible to search engines. Moving between countries can shift latency-sensitive metrics a little, so choose a region close to your actual audience at /locations.
How do I migrate a database without losing data?
Stop writes on the source before starting writes on the destination — that ordering is the entire guarantee. For small datasets, freeze, dump with pg_dump or mysqldump --single-transaction, restore, and switch. For larger ones, replicate in advance and promote the replica at cutover so the catch-up takes seconds. Verify by comparing row counts on the tables that matter before you send any traffic.
Can I move to an offshore host without giving them my identity?
Yes. A host that bills from a crypto balance has no card and no billing address to attach to the machine, so the account behind the server holds nothing about you. The migration itself is technically identical — the same file passes, the same dump, the same DNS switch. /offshore-vps covers how the model works and /pay-with covers the payment side.
When is it safe to cancel the old server?
After its access log has been quiet for at least a day, every credential it held has been rotated, and you have taken a backup of the new server and restored it somewhere successfully. One extra month of a small VPS is the cheapest insurance in the process — it is the difference between a rollback and an incident.


