Who this is for. MSP project leads and engineers who are moving an existing client estate (from on-premises servers, another host or a previous provider) onto a platform you operate, and who want the move to be boring. A migration has three parts: copy the data, switch the users, and be able to go back. The third part is the one that gets skipped.
Discovery: find what nobody wrote down
The migration plan is only as good as the inventory it starts from. Per server, capture:
- Role, operating system and version, installed applications and their versions.
- Resource use at peak, using the method in virtual servers and capacity planning, so you can place it on the target.
- Data volume, rate of change, and the largest single file or database.
- Network facts: IP address, whether it is hard-coded anywhere, firewall rules in and out, DNS names that point to it.
- Dependencies: what talks to it, and what it talks to. Include scheduled tasks, mail relays, license servers, printers and scanners, service accounts, certificates and anything that authenticates against it.
- Licensing and support position, including whether the vendor supports the application in the target environment.
- Business facts: who owns it, who signs off after the move, the acceptable outage window and what the business cost of exceeding it is.
Hidden dependencies are the usual cause of a failed cutover. Gather them by reading firewall logs and connection tables for a few weeks, rather than relying on memory. Two read-only commands help for a Linux or Windows server you administer:
# Linux: who is connected to this server right now, grouped by remote address
sudo ss -tn state established | awk 'NR>1 {print $4}' | sed 's/:[0-9]*$//' | sort | uniq -c | sort -nr | head
# Windows: established TCP connections, with the owning process
Get-NetTCPConnection -State Established | Sort-Object RemoteAddress |
Select-Object LocalPort, RemoteAddress, RemotePort, OwningProcess
A snapshot is a start, not a survey. Repeat over a full business cycle, including month-end if that is when the odd job runs.
Wave design: order by dependency and risk
Do not migrate everything at once and do not migrate in alphabetical order. Group servers into waves by dependency (things that depend on each other move together or in a defined order) and by risk (low-risk first, to find mistakes cheaply).
| Wave | Contents | Purpose |
|---|---|---|
| 0 | A non-critical server, or your own test tenant | Prove the platform, the network design and the runbook |
| 1 | Low-risk, low-dependency systems | Exercise onboarding, backup and monitoring at small scale |
| 2 | Shared services (identity, file, print) | Move what many things depend on, with extra care and rollback readiness |
| 3 | Applications and databases | The business-critical systems, once the supporting pieces are proven |
| 4 | Decommission | Retire the source only after an agreed acceptance period |
Keep waves small enough that you can roll one back without rolling back the others.
Choosing how to move the data
| Approach | Good for | Watch for |
|---|---|---|
| Convert and restore the whole virtual machine | Servers with complicated software, little downtime sensitivity | Driver and hardware-dependent configuration; the downtime is the copy time |
| Replicate continuously, then cut over | Larger systems and tight outage windows | Network bandwidth, and replication that adds load to the source |
| File-level synchronisation | File servers | Permissions, hard links, open files and exact security descriptors |
| Application-level migration (native database replication, application export) | Databases and applications with supported migration paths | Versions, features that differ between source and target |
| Rebuild fresh and migrate the data | Old systems you do not trust | Time, and hidden configuration lost in the rebuild |
Each approach has a seed (the long first copy while the source stays live) followed by one or more delta passes, with a final short pass during the outage window. The shorter the delta, the shorter the outage.
A file-level example with sample paths and hostnames. Run the dry run first and read what it says:
# Dry run: shows what WOULD be copied, changes nothing
rsync -aHAXn --numeric-ids --info=progress2 -e ssh /srv/data/ migr@new-host:/srv/data/
# Real seed copy (no --delete). Re-run the same command for each delta pass.
rsync -aHAX --numeric-ids --info=progress2 -e ssh /srv/data/ migr@new-host:/srv/data/
Adding --delete to rsync, or using /MIR with robocopy, removes files on the destination that are not on the source. Use it only on a final pass, only when you are certain of the direction and the paths, and only after a dry run.
# Windows file server: first, list only (no copy), then copy with security descriptors preserved
robocopy \\old-fs\share D:\share /E /COPYALL /DCOPY:DAT /R:1 /W:1 /L /LOG:C:\migr\dryrun.log
robocopy \\old-fs\share D:\share /E /COPYALL /DCOPY:DAT /R:1 /W:1 /LOG+:C:\migr\copy.log
For disk images, qemu-img converts formats without altering the source, and writes a new file:
qemu-img info source.vmdk
qemu-img convert -p -f vmdk -O qcow2 source.vmdk dest.qcow2
qemu-img check dest.qcow2
Verify copies with checksums rather than trusting file counts:
# On the source
(cd /srv/data && find . -type f -print0 | sort -z | xargs -0 sha256sum > /tmp/manifest.sha256)
# Copy manifest.sha256 to the target, then there:
(cd /srv/data && sha256sum -c --quiet /tmp/manifest.sha256)
Estimating the seed and the outage
Two numbers drive the plan: how long the seed copy takes and how long the final outage lasts. Estimate both from measurement, not from the link speed printed on the contract. Copy a representative sample, note the sustained rate you actually achieve (small files are usually far slower than large ones), and divide.
Worked example with invented numbers. A file server holds 2 TB (about 2,000,000 MB) and a test copy sustains 100 MB/s. The seed takes 2,000,000 / 100 = 20,000 seconds, roughly 5.6 hours, and you can run it while users work. Suppose users change 20 GB a day. The final delta of one day’s changes is 20,000 MB / 100 MB/s = 200 seconds plus scanning time, so the outage window is dominated by scanning, stopping services and verification rather than by data. If the sustained rate were ten times lower, the seed would take more than two days and you would need to start sooner and run several delta passes, which is a different plan. Estimating first tells you which plan you are in.
Telling the client what will happen
A technically clean cutover still fails if the client is surprised. Agree in writing, before the night: the outage window and the decision point, the acceptance test and who runs it, what the client must do (log off, stop a batch job, confirm that nobody is working late), and who is reachable by phone throughout. Agree a change freeze on the source and on anything that depends on it from the final delta until acceptance. After the move, send a short summary of what changed (addresses, names, backup schedule, support contacts) so that the next incident does not begin with confusion about where the server lives.
Network and name cutover
Decide how users will find the server after the move:
- Keep the IP address by extending the network, or by moving the address with the server. It reduces the number of things that change. It also means your platform must carry the client’s address plan; see the address-planning notes in private cloud.
- Change the address and update DNS. Lower the DNS time-to-live well before the cutover, by at least the old TTL, so that caches have expired by the time you switch. Check what resolvers actually hold:
dig +noall +answer app.example.com A # the number before IN is the remaining TTL in seconds
Resolve-DnsName app.example.com -Type A # Windows; shows TTL as well
Test-NetConnection app.example.com -Port 443
- Plan the certificates, firewall rules, VPNs and any IP allow-lists at partners that reference the old address. Those are outside your control and often the slowest to change; start early.
A cutover runbook skeleton
Write the runbook before the night and rehearse it in wave 0. Each line has an action, an owner, a check and a rollback trigger:
| When | Step | Owner | Check | Roll back if |
|---|---|---|---|---|
| T-7 days | Lower DNS TTL; confirm seed copy complete | Engineer | TTL reflects change; delta size known | Seed not complete |
| T-1 day | Final delta; backup of source and target | Engineer | Backup verified | Backup fails |
| T-0, start | Stop writes on source; final delta pass | Engineer | Checksums match | Mismatch unexplained |
| T-0 | Start target; change DNS or address | Engineer | Services start; names resolve | Service fails to start |
| T+30 min | Client acceptance test | Client contact | Agreed test script passes | Test fails |
| T+1 hour | Go or roll back decision | Project lead | Named decision | Per script |
| T+1 day | Backups, monitoring and patching confirmed on target | Engineer | Reports show target | n/a |
Set a decision point with a time, and have the decision maker named. Without it, the team will spend three hours debugging at the point where the right call was to go back.
Rollback design
A rollback is only real if you have kept the source intact and decided what happens to data written after the cutover. Leave the source powered off or isolated but unchanged until the acceptance period ends. If users have already written new data on the target when you roll back, you have a reverse-sync problem; decide in advance whether that data matters and how it returns. For databases, rolling back after the application has run on the target often needs a replication path in the other direction, so plan it before you need it.
Onboarding to the platform after the move
The move is not finished until the server is a platform citizen:
- Backup in place at the right tier, with a first restore test, per backup and recovery.
- Monitoring tagged with the tenant, and alert routes confirmed.
- Patch group and maintenance window agreed with the client.
- Address register, documentation and administrator access updated and reviewed.
- Resource limits set to match what you sold, and metering started so billing matches.
- The source retired on schedule, with its data securely deleted and the deletion recorded.
Common mistakes
- No acceptance test written in advance, so “it works” is an opinion at 2 a.m.
- Skipping the dry run and copying in the wrong direction.
- Discovering a hard-coded IP address during the outage window.
- Changing too much at once: new platform, new addressing, new software version in a single night.
- Decommissioning the source early because the cutover looked fine.
- Forgetting that the contract for the old provider has notice periods, and overlapping cost while you migrate. Check the exit terms in evaluating a hosting provider.
What to do next
Write the inventory for your next client move, group it into waves, and draft the runbook before you schedule anything. The requirements planner helps you state what the receiving platform must provide before you commit a tenant to it.