--- title: "Server Migration Checklist: Plan, Move and Verify" description: "A practical server migration checklist: inventory the old server, prepare the new one, copy data, cut over with a rollback plan and verify everything after." category: "Server Migration" language: en source: https://cubepath.com/docs/server-migration/complete-migration-checklist updated: 2026-09-25 --- # Server Migration Checklist: Plan, Move and Verify Most failed server migrations are not caused by a hard technical problem but by something nobody wrote down: a cron job, a firewall rule, a certificate renewal hook or a service listening on an unexpected port. This checklist walks you through a Linux server migration in the order you actually do it, from taking an inventory of the old server to decommissioning it, with the commands that collect each piece of information. It assumes Ubuntu or Debian on both sides, but the phases apply to any distribution. Use it together with the detailed guides for each data type: databases, mail servers, Docker containers and large file transfers with rsync. ## Prerequisites - SSH access with `sudo` to the **old server** and the **new server**, for example a CubePath VPS running Ubuntu 24.04 LTS. - Access to the DNS zone of every domain the server handles. - A place to store the inventory, such as a text file or a ticket, that everyone involved can read. ## Step 1 - Taking an inventory of the old server You cannot migrate what you do not know exists. Save the output of each command below into your inventory. Run them on the old server. ### System and resources ```bash cat /etc/os-release nproc free -h df -hT -x tmpfs -x devtmpfs ``` Size the new server from real usage, not from the old server's specification: `free -h` and `df -h` tell you how much memory and disk the workload uses today. ### Installed software ```bash apt-mark showmanual > ~/inventory-packages.txt ls /etc/apt/sources.list.d/ ``` `apt-mark showmanual` lists the packages someone installed on purpose, which is far more useful than the full package list. The files in `sources.list.d` show third-party repositories (PHP, Node.js, Docker, database vendors) you will need to add again. Also look for software installed outside the package manager in `/opt` and `/usr/local/bin`. ### Services and listening ports ```bash systemctl list-units --type=service --state=running --no-pager sudo ss -tlnp sudo ss -ulnp ``` Every listening port should map to a service you know about. Pay special attention to services bound to `127.0.0.1` (databases, caches), because the application expects them on the same machine. ### Scheduled tasks ```bash sudo ls /var/spool/cron/crontabs/ sudo crontab -l ls /etc/cron.d /etc/cron.daily /etc/cron.hourly systemctl list-timers --all --no-pager ``` Check the crontab of each user listed in `/var/spool/cron/crontabs/` with `sudo crontab -l -u username`. Forgotten cron jobs (backups, invoice runs, cleanups) are the most common thing to go missing after a migration. ### Configuration, users and secrets - Web server virtual hosts (`/etc/nginx/sites-enabled/`, `/etc/apache2/sites-enabled/`) and PHP pools. - TLS certificates and how they renew (`sudo certbot certificates`, `/etc/letsencrypt/renewal/`). - Application `.env` files and configuration files with credentials. - Local users that own files or run services: `awk -F: '$3 >= 1000 && $3 < 65534 {print $1, $3}' /etc/passwd`. - SSH authorized keys in each user's `~/.ssh/authorized_keys`. - Firewall rules: `sudo ufw status numbered` or `sudo iptables-save`. ### Data locations and sizes ```bash sudo du -sh /var/www /var/lib/mysql /var/lib/postgresql /var/lib/docker /home /srv 2>/dev/null ``` The size of the data decides how long the copy takes, and therefore how you plan the cutover. ### External dependencies Write down everything outside the server that knows its IP address: DNS records, firewall allowlists at partners or payment providers, API keys restricted by IP, monitoring checks, backup jobs that pull from it, and the reverse DNS record if the server sends mail. ## Step 2 - Planning the migration With the inventory in hand, decide the following and write it down: | Decision | What to write down | |---|---| | Migration method | For each data type: rsync, database dump or replication, container volumes. | | Downtime window | A date and time with low traffic, and the maximum acceptable downtime. | | Cutover mechanism | Which DNS records change, or which load balancer or floating IP moves. | | Rollback trigger | The condition that makes you go back, for example "checkout fails after 30 minutes". | | Rollback steps | How to point traffic back to the old server. It must stay untouched until the end. | | Owners | Who executes, who tests, who communicates with users. | Two things must happen in advance: - **Lower DNS TTLs** of every record that will change to `300` seconds, at least 24 to 48 hours before the cutover, so the change propagates quickly. Check the current TTL with `dig +noall +answer your_domain A`. - **Tell users** about the maintenance window if there will be visible downtime. ## Step 3 - Preparing the new server Build the new server from the inventory, not by copying the whole root filesystem. Copying `/` between different machines carries over stale network configuration, machine IDs and old packages. - [ ] Operating system updated: `sudo apt update && sudo apt full-upgrade`. - [ ] Time zone and hostname set: `sudo timedatectl set-timezone ...`, `sudo hostnamectl set-hostname ...`. - [ ] Non-root sudo user created, SSH keys installed, password login disabled. - [ ] Firewall configured with only the ports from the inventory: `sudo ufw allow OpenSSH`, `sudo ufw allow 80,443/tcp`, then `sudo ufw enable`. - [ ] Third-party repositories added with keyrings in `/etc/apt/keyrings/`, and packages from `apt-mark showmanual` installed. - [ ] Same major versions of the runtime (PHP, Node.js, Python) and database, unless the migration is also an upgrade that you have tested separately. - [ ] Service users created with the same UIDs and GIDs as on the old server, so copied files keep the right owners. - [ ] Configuration files copied and adapted (paths, IP addresses, host names). Search for the old IP: `sudo grep -rn "old_server_ip" /etc`. - [ ] Cron jobs and systemd timers recreated. - [ ] Each service checks its configuration cleanly, for example `sudo nginx -t`, `sudo apachectl configtest`, `sudo postfix check`. ## Step 4 - Copying the data and testing Do a first full copy of the data while the old server is still in production, so the final copy during the cutover is short: - **Files**: `rsync -aHAX --numeric-ids` with a final pass using `--delete`. - **Databases**: a consistent dump (`mysqldump --single-transaction`, `pg_dump -Fc`), or replication if downtime must be minimal. - **Containers**: images from a registry or `docker save`, volumes as tar archives. Then test the new server before any real user reaches it. Point your own computer to the new IP by adding a line to your local `hosts` file (`/etc/hosts` on Linux and macOS, `C:\Windows\System32\drivers\etc\hosts` on Windows): ```text new_server_ip your_domain www.your_domain ``` Go through the test list: - [ ] Home page and main user flows work (login, search, forms, checkout). - [ ] File uploads work and are saved with the right permissions. - [ ] The application writes to the new database, not the old one (check the connection host in its configuration). - [ ] Outgoing email works: `journalctl -u postfix` or the application's mail log. - [ ] Scheduled tasks run: `systemctl list-timers` and the logs of each cron job. - [ ] Logs are clean: `sudo journalctl -p err -b` shows no new errors. Remove the `hosts` entry when you finish testing. ## Step 5 - Cutting over On the day of the migration, follow the plan in order: 1. Confirm that the DNS TTLs are low: `dig +noall +answer your_domain A`. 2. Put the application in maintenance mode or stop it on the old server, so data stops changing. 3. Run the final data sync: rsync with `--delete`, a new database dump or the replication switchover. 4. Start the services on the new server and run the quick tests from Step 4 again. 5. Change the DNS records (or move the floating IP) to the new server. 6. Watch the logs on both servers. Traffic on the old one should drop to almost nothing within a few TTL periods: `sudo tail -f /var/log/nginx/access.log`. If the rollback trigger is met, point DNS back to the old server and start its services again. Because you did not change anything on it, it is still a known good state. Any data written on the new server in the meantime must be copied back, which is why the rollback decision should be taken early. ## Step 6 - Verifying after the migration In the hours and days after the cutover, check: - [ ] DNS resolves to the new IP from outside your network: `dig @1.1.1.1 +short your_domain`. - [ ] TLS certificates are valid and renewal works: `sudo certbot renew --dry-run`. - [ ] Response times and error rates are similar to before, in your monitoring or access logs. - [ ] Cron jobs ran at their scheduled time. - [ ] Backups of the new server are running and a test restore works. - [ ] Monitoring checks point to the new server and alerts reach you. - [ ] Partners and external services with IP allowlists have been updated. ## Step 7 - Decommissioning the old server Do not rush this step. Keep the old server stopped but intact for one to two weeks, until you are sure nothing is missing. Before deleting it: - [ ] Take a final backup or snapshot, and store it for as long as your data retention policy requires. - [ ] Remove the old IP from SPF records, allowlists and monitoring. - [ ] Revoke credentials that only the old server used (API keys, database replication users, SSH keys). - [ ] Raise DNS TTLs back to their usual values, for example `3600`. - [ ] Update your documentation with the new server's details. ## Conclusion A migration goes smoothly when it is mostly preparation: a complete inventory, a new server built from it, a first data copy while the old server is still live, and a short cutover with a rollback plan that keeps the old server untouched. Keep the inventory you produced here; it is the documentation of the new server and the starting point for the next migration. For each data type, continue with the dedicated guides on database migration, Docker container migration and large file transfers with rsync.