VPS Server Migration Without Data Loss: Step-by-Step Guide

VPS Server Migration Without Data Loss: Step-by-Step Guide
Quick Answer
Migrating a VPS server without data loss happens in three stages: you copy files via rsync over SSH with permissions preserved, you transfer the database through a dump (mysqldump or pg_dump), and at the final step you stop writes on the old server and sync the remaining difference before switching DNS. With this approach you can avoid downtime almost entirely. Data isn’t lost because you don’t tear down the old server until you’ve confirmed the new one works identically.

1. Diagnosis

Your old VPS is on its last legs. Or the hosting provider raised prices. Or you’ve finally decided to get off that single-core instance where the site crashes under three simultaneous visitors. The reason doesn’t matter. What matters is that you need to migrate your VPS server without data loss right now, not “whenever I get around to it.

A familiar scene: you copied files over FTP, forgot the database, forgot the cron jobs, forgot the directory permissions, and an hour later angry messages from the client are flying into the support chat. We’re not doing that here.

What you’ll get at the end: a working site on the new server, the old server intact in case you need to roll back, and not a single lost database record. Time to migrate: anywhere from 40 minutes for a simple brochure site to 3-4 hours for a project with a large database and a bunch of small services.

You’ll need:

  • root access to both servers over SSH
  • an SSH key or password for root on the new VPS
  • access to the domain’s DNS zone
  • a window for the final sync (usually 5-15 minutes of write downtime, not downtime for the whole site)

In this article we’ll cover: why migrations usually don’t go as planned, a step-by-step recipe using rsync and a database dump, how to verify the result, common mistakes with fixes, alternative migration methods, and what to do afterward so you don’t hit the same snags six months from now.

2. Why VPS migrations usually break

Before you jump into the console, figure out where most migration problems actually come from. Then the recipe in section 3 won’t be a pile of magic commands, it’ll be a clear algorithm.

Cause Why it breaks the migration
Different software versions on old and new server PHP 8.3 vs PHP 7.4, MySQL 8 vs MariaDB 10.6: database syntax or extensions may not match, the site crashes with a 500 error
Copying files without stopping writes While rsync is copying, the database or files keep getting written to. Some changes get lost between the start of the copy and the final cutover
Forgotten cron jobs crontab is tied to a user on a specific machine; it doesn’t get copied automatically when you move site files
DNS record TTL set to 24 hours Some users keep hitting the old server for hours after the cutover, which is confusing when you’re checking the result
Lost file permissions and ownership rsync without the right flags copies files as the current user, and the web server loses access to uploaded files
SSL certificate tied to the old IP Let’s Encrypt stores challenge files and config at specific paths. A simple copy may not pick up the certificate correctly
Forgotten mail and SPF/PTR records The new server’s IP has no reputation yet; mail sent from it lands in spam if you don’t set up a reverse DNS record in advance

None of these causes is fatal on its own. Ignoring them is.

3. The recipe

Preparation

Dependencies: an SSH client on your local machine or direct access from one VPS to the other, rsync on both servers, access to the DNS zone management panel.

Component Current version at time of publication
rsync 3.4.3
rclone (for cloud storage, optional) 1.75.0
New server OS same major version as the old one, or newer (Ubuntu 22.04/24.04, Debian 12)
Check before you start
Before installing any component, check the latest releases in the official repositories. rsync and rclone versions get security patches, and an old rsync version may not support the delta-transfer flags you need.

Check the environment on the new server: how much free space, which kernel version, and whether SSH key access works.


df -h
uname -r
ssh -T root@NEW_SERVER_IP

Migration architecture

%%{init: {
  'theme': 'base',
  'themeVariables': {
    'primaryColor': '#ffffff',
    'primaryTextColor': '#1e293b',
    'primaryBorderColor': '#94a3b8',
    'lineColor': '#64748b',
    'fontSize': '15px',
    'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
  },
  'flowchart': {'curve': 'linear', 'nodeSpacing': 50, 'rankSpacing': 50}
}}%%
flowchart TD
    A["Old VPS"] --> B["rsync over SSH"]
    B --> C["New VPS"]
    A --> D["Database dump"]
    D --> C
    C --> E["Verify via hosts file"]
    E --> F["DNS cutover"]
    F --> G["Client"]
    style A fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#9a3412
    style C fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
    style E fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
    style G fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af

Step 1. Inventory

Make a list of what you’re actually migrating. Not “the site,” but specifically: the path to the files, the database name, the DB user, cron jobs, SSL certificates, web server configs.


crontab -l -u www-data
ls -la /etc/nginx/sites-enabled/
mysql -e "SHOW DATABASES;"
ls /etc/letsencrypt/live/

Result: a text checklist that keeps you from forgetting half the service halfway through the migration.

Step 2. Install matching software on the new server

Install the same major versions as on the old server. Don’t install MySQL 8 if the old server runs MariaDB 10.6, these are different dialects and the dump may fail to load without edits.


apt update && apt install -y nginx mariadb-server php8.1-fpm php8.1-mysql unzip
systemctl status nginx mariadb php8.1-fpm

Result: a clean stack on the new server, ready to receive files and the database without version conflicts.

Step 3. Transfer files with rsync

rsync beats scp and FTP for one reason: it copies the delta, not everything from scratch on repeat runs. The first run can take a while; subsequent ones take minutes.


rsync -avz --progress -e "ssh -p 22" \
  /var/www/site/ root@NEW_SERVER_IP:/var/www/site/

The -a flag preserves permissions, ownership, and symlinks. Without it you get a site that opens fine, but the web server can’t write to the uploads directory.

Result: files on the new server with permissions preserved, identical to the original.

Step 4. Dump and transfer the database

Run the database dump straight through the SSH pipe, without an intermediate file on disk. Faster, and it doesn’t eat up disk space.


mysqldump -u root -p --single-transaction --routines --triggers site_db | \
  ssh root@NEW_SERVER_IP "mysql -u root -p site_db"

The --single-transaction flag is critical for InnoDB. It takes a consistent snapshot without locking tables for writes. Without it, on a busy database you’ll get tables out of sync with each other.

The logic is the same for PostgreSQL:


pg_dump -U postgres site_db | ssh root@NEW_SERVER_IP "psql -U postgres site_db"

Result: a full copy of the database on the new server, including stored procedures and triggers.

Step 5. Migrate configs, cron, and certificates

Copy the nginx or Apache config separately, not together with the site files. It contains absolute paths and possibly the old IP.


rsync -avz /etc/nginx/sites-available/site.conf root@NEW_SERVER_IP:/etc/nginx/sites-available/
crontab -l -u www-data > cron_backup.txt
scp cron_backup.txt root@NEW_SERVER_IP:/tmp/

On the new server:


crontab -u www-data /tmp/cron_backup.txt
ln -s /etc/nginx/sites-available/site.conf /etc/nginx/sites-enabled/
nginx -t

It’s easier to issue the SSL certificate fresh than to copy it. Use certbot after the DNS cutover, it’s more reliable than dragging a private key manually between servers.

Step 6. Final synchronization

This is where it’s decided whether you lose data or not. The logic is simple: time passes between the first rsync run and the DNS cutover, during which something changes on the old server. So you need a second, short run right before the cutover.

Critical for zero data loss
Before the final sync, put the site into read-only mode or temporarily disable writes to the database. Otherwise changes users made in the last few minutes may not make it into the final dump.

rsync -avz --delete -e ssh /var/www/site/ root@NEW_SERVER_IP:/var/www/site/
mysqldump -u root -p --single-transaction site_db | \
  ssh root@NEW_SERVER_IP "mysql -u root -p site_db"

This run takes seconds to minutes, because it copies only the delta. That’s exactly why the downtime window is 5-15 minutes, not hours.

Step 7. Verify the new server before the DNS cutover

Test the site on the new IP by overriding the hosts file on your local machine. That way you confirm everything works without touching production DNS yet.


echo "NEW_SERVER_IP example.com" | sudo tee -a /etc/hosts
curl -I -H "Host: example.com" http://NEW_SERVER_IP

Open the site in a browser, click through the key pages, check the contact form and the admin panel. If something’s off, fix it now while clients are still hitting the old server.

Step 8. DNS cutover

A day or two before the migration, lower the A record’s TTL to 300 seconds. This shortens the “blurry” period where some users see the old server and some see the new one.


dig +short example.com

Change the A record to the new server’s IP in your DNS provider’s panel, then wait for resolution to update worldwide.

%%{init: {
  'theme': 'base',
  'themeVariables': {
    'primaryColor': '#f8fafc',
    'primaryTextColor': '#1e293b',
    'primaryBorderColor': '#94a3b8',
    'noteBkgColor': '#fefce8',
    'noteTextColor': '#713f12',
    'noteBorderColor': '#fbbf24',
    'actorBkg': '#f8fafc',
    'actorBorder': '#94a3b8',
    'actorTextColor': '#1e293b',
    'fontSize': '15px',
    'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
  },
  'sequence': {
    'mirrorActors': false,
    'messageAlign': 'center',
    'actorMargin': 120,
    'width': 160,
    'noteMargin': 12
  }
}}%%
sequenceDiagram
    participant U as User
    participant D as DNS
    participant O as Old VPS
    participant N as New VPS
    U->>D: Request IP for example.com
    D->>U: Returns new IP
    U->>N: Request page
    N->>U: Response 200 OK
    Note over O: Stays as a fallback for a couple of days

Step 9. Monitoring after the cutover

The first few hours after the cutover are the tensest. Watch the logs in real time and don’t remove the old server yet.


tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log

Keep the old VPS alive for at least 3-5 days. If something surfaces, you can roll back in minutes instead of rebuilding from scratch.

4. Verification

After the migration, run more than just “did the site load.” Run a full set of checks.


systemctl status nginx mariadb php8.1-fpm
nginx -t
mysqlcheck -u root -p --all-databases
curl -o /dev/null -s -w "%{http_code}\n" https://example.com

Compare checksums of key files on the old and new server. Cheap insurance against “half the files didn’t copy over.”


rsync -avzn --checksum -e ssh /var/www/site/ root@NEW_SERVER_IP:/var/www/site/

The -n flag is a dry run: it won’t change anything, just shows the differences. Empty output means the files are identical.

5. Troubleshooting

Error Cause Fix
After the DNS cutover, some users still see the old site TTL wasn’t lowered beforehand, DNS cache at ISPs Wait out the old TTL, check resolution from different locations
502 Bad Gateway on the new server PHP-FPM isn’t running, or the socket path is wrong in the nginx config Check the socket path and the service status
The site loads but the admin panel returns 500 Different PHP versions, a missing extension Compare the list of PHP modules on the old and new server
Uploaded files (images, documents) won’t open rsync copied them with the current user’s permissions, not the web server’s Reassign directory ownership
Cron jobs stopped running crontab wasn’t migrated for the right user Import the saved crontab
Mail from the site lands in spam New IP has no reputation, PTR record not configured Request a reverse DNS record from the host, add SPF and DKIM
SSL certificate is invalid on the new server Certificate tied to the old path, or the challenge domain didn’t pass Reissue the certificate via certbot after the DNS cutover

chown -R www-data:www-data /var/www/site/uploads
certbot --nginx -d example.com

It’s not as bad as you think. It’s a lot worse if you skipped step 6 and didn’t do the final sync. Then part of the data is gone for good, and no troubleshooting fixes that.

6. Alternatives

rsync plus a database dump isn’t the only way. Here are other options and why this one wins in most cases.

Method When it fits Downside
Host snapshot/image Migration within the same hosting provider, identical environment Doesn’t work between different hosts and hypervisors, often with no guarantee of database consistency
Block-level disk cloning (dd, partclone) Fully identical hardware and disk layout Requires a full server shutdown during cloning, inflexible when changing OS or disk size
rclone Migrating static assets through an intermediate cloud storage An unnecessary middleman for a plain two-VPS migration, only worth it when moving through S3-compatible storage
CMS migration plugins (for WordPress and similar) A simple brochure site without custom code Works poorly with large databases and non-standard file structures

rsync plus a database dump wins overall: it works between different hosting providers, different OSes, different disk versions, gives you control over every step, and lets you copy over only the changes at the final stage. That’s why it’s the baseline recipe, not just one option among many.

7. Prevention

The migration is done. Don’t relax yet.

  • Set up monitoring for the new server (Zabbix, Netdata, or a simple uptime checker) on day one
  • Take a backup right after the migration, for example via BorgBackup, so you’re not starting your backup history from zero
  • Check that all services start automatically after a reboot
  • Make sure the new server is secured at least as well as the old one, ideally stricter
  • Don’t delete the old VPS for at least a week, even if you’re itching to save money
  • Update MX and SPF DNS records if mail moved too
  • Verify that backups from the new server go to separate storage, not just sitting locally

It only takes one small thing to ruin everything. One forgotten backup cron job, and your entire six-month archive of data is gone.

Securing the new server

  • UFW: close everything except the ports you need, right after install
  • fail2ban: on SSH and on web login forms
  • SSH hardening: disable password login, change the port, disallow direct root login
  • A separate DB user with permissions only on the needed database, not access to all databases at once
  • Regular backups stored outside the server itself
  • IP-restricted access to admin panels wherever possible

ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
apt install fail2ban -y
systemctl enable fail2ban

Backups

What to back up How often Where
Site files Daily Separate storage, not the same disk
Database Daily, more often under heavy load Separate storage plus an off-site copy
Service configs After every significant change Git repository or separate storage

Test restoring from backup by hand at least once a quarter. A backup that’s never been restored isn’t a backup, it’s a file taking up space.

Port reference

Port Protocol Purpose Exposed externally?
22 TCP SSH access, used for rsync and transferring the dump Yes, restricted by IP or key
80 TCP HTTP, redirects to HTTPS Yes
443 TCP HTTPS Yes
3306 TCP MySQL/MariaDB, needed only for direct remote database connections No, localhost only or via an SSH tunnel

Updating after the migration

Update the system on the new server as soon as it’s stable, but not on migration day itself. Give the service a couple of days to run without changes first.


apt update && apt list --upgradable
apt upgrade -y

Before updating, check the list of packages it will touch and take a snapshot or back up your configs. If something breaks after the update, roll back to your saved backup instead of guessing which package is at fault.

8. FAQ

How do you migrate a VPS without taking the site down?
You can only migrate with zero downtime using a blue-green deployment with a load balancer. In a typical scenario, downtime comes down to 5-15 minutes for the final database sync, enough time to copy the delta rather than the whole dataset again.

How long does a VPS server migration take?
For a small site with a database up to a few gigabytes: 30-60 minutes total. For a project with a large database and many services: 2 to 4 hours, including verification and the DNS cutover.

How do you verify a VPS migration happened without data loss?
Compare file checksums via rsync with the –checksum flag in dry-run mode, check row counts in key database tables with SELECT COUNT(*), and compare directory sizes on the old and new server.

What do you do if the site doesn’t load after a VPS migration?
Check the status of nginx and PHP-FPM, look at error.log, and confirm DNS resolution has already updated on your machine. In most cases the cause is an incompletely copied web server config or a PHP version mismatch.

How is migrating via rsync different from migrating via a host snapshot?
A snapshot copies the entire disk and only works within one host with a compatible hypervisor. rsync copies files at the filesystem level, works between any servers and OSes, and lets you copy over only the changed files at the final step.

Do you need to change passwords and SSH keys after moving to a new VPS?
Yes. A migration is a good excuse to generate a new SSH key pair for the new server instead of dragging old passwords along out of habit. While you’re at it, review the list of users with access, old forgotten accounts from three years ago often linger there.

9. Outcome

Files have been migrated via rsync with permissions preserved, the database has been transferred via dump with a final sync, and DNS now points to the new IP. The old server sits there as insurance. No need to touch it until the new one has run a few days without surprises.

What’s left is routine: monitoring, regular backups, scheduled updates. If something went wrong after your migration, drop a comment and we’ll work through it together.

Оставайтесь на связи

Рецепты от IT-боли. Без воды, без рекламы, без маркетинговой шелухи.

Подписаться на IT-Аптеку →
Поделитесь:

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top