VMHeaven

Guides

Moving a website from shared hosting to a VPS without downtime

Inventory, TTLs, rsync, mysqldump and a cutover you test before DNS changes — the staged migration that moves a site onto a VPS with no visible downtime.

Updated 20 Sept 2026~11 min read

A migration from shared hosting to a VPS fails in a small number of predictable ways: DNS changes before the new server is ready, mail stops arriving and nobody notices for a day, the PHP version is half a release off, or the database comes across in the wrong character set and every accented letter turns into mojibake. All four are avoided by the same thing — doing it in an order where the new server is proven before anything points at it.

This is that order. Done this way the actual cutover is a DNS change that takes seconds, with a tested rollback behind it.

Inventory: what you actually have

Half of all migration surprises are things nobody knew were running. Before anything else, write down what is on the old account — and check rather than remember:

on the old host, over SSH or the panel's terminal
php -v                      # major AND minor version
php -m                      # loaded extensions — this is the list that bites
mysql -e "SELECT VERSION()"
crontab -l                  # scheduled jobs nobody documented
du -sh ~/public_html        # how much data is actually moving
du -sh ~/mail 2>/dev/null   # and how much of it is mail

Then the things no command prints for you:

  • Every DNS record, not just the A record — mail, SPF, DKIM, DMARC, verification TXT records for search consoles and third-party services, and any subdomain pointing somewhere else entirely.
  • Mailboxes, forwarders and autoresponders, with sizes. This is usually the largest and most painful part of the move.
  • Anything keyed to the old IP address — API allowlists, payment gateway restrictions, a partner’s firewall rule. These break silently on the new IP and are found by users, not by you.
  • Your .htaccess files in full. Rewrite rules, redirects, expiry headers and access restrictions all live there, and on nginx none of them exist.

Decide early what you are not moving

Mail is the honest hard part, and the best answer is usually to not move it onto the VPS at all.

A brand-new IP address has no sending reputation. A self-run mail server needs correct forward and reverse DNS, SPF, DKIM and DMARC, spam filtering in both directions and ongoing attention, and getting any of it wrong means your invoices land in spam. Two far better options: leave MX pointing at the old provider (many will keep mail running after you move the website), or move mail to a provider whose only job is mail. Either way you have removed the single most fragile component from the migration.

If you do move mailboxes yourself, use an IMAP sync tool rather than copying files between two mail stores, and do it while both sides are still live.

The migration, in order

  1. 1Build the new server before you touch anything else

    Provision the VPS, then do the base work: a non-root sudo user, key-only SSH, a closed firewall, automatic security updates. The initial server setup guide covers it and takes about fifteen minutes.

    Then match the old stack deliberately. Install the same PHP major and minor version the old host runs and the extensions php -m listed — a missing extension is the most common cause of a blank page after a move. Install the same database major version. And pick your web server by how much of your configuration lives in .htaccess: if the answer is “a lot”, install Apache and the move is a copy; if it is “almost nothing”, nginx is the better long-term host and you translate a handful of rules.

  2. 2Lower the DNS TTL — days ahead, not hours

    This is the step people skip and then regret. Lowering the TTL only helps if the old TTL has expired everywhere first: resolvers that already cached the record at 86400 seconds will keep it for up to a day no matter what you change now.

    So at least one full old-TTL period before the move — a day ahead if the TTL is 86400 — set the TTL on the records you will change to 300 seconds. Confirm what the authoritative server is handing out, rather than what a cache tells you:

    ask the authoritative nameserver directly
    dig NS example.com +short
    dig @ns1.oldhost.example example.com A +noall +answer

    The number in the second column of the answer is the TTL. From a caching resolver it counts down; from the authoritative server it is the configured value. Raise it back to something sensible a week after the cutover.

  3. 3Copy the files

    With SSH on the old host, rsync is the tool — it resumes, it only sends differences on the second run, and that second property is what makes the near-zero-downtime cutover possible later:

    first pass, while the old site is still live
    rsync -azP --delete \
      --exclude 'wp-content/cache/' --exclude 'var/cache/' --exclude '*.log' \
      -e ssh [email protected]:~/public_html/ /var/www/example.com/

    The trailing slashes are load-bearing: src/ copies the contents of the directory, src copies the directory itself into the destination. Getting that wrong produces /var/www/example.com/public_html/ and a puzzling 403.

    No SSH on the old host? Produce an archive through the panel’s file manager or backup tool and download it once — a single compressed file transfers far faster over FTP than thousands of small ones:

    fallback via archive
    # on the old host, through the panel's terminal or backup feature
    tar -czf site.tar.gz --exclude='*/cache/*' public_html
    
    # on the new server
    tar -xzf site.tar.gz -C /var/www/example.com --strip-components=1

    Then fix ownership in one go, because files that arrive owned by the wrong user are the second most common cause of a post-migration 403:

    ownership and sane permissions
    sudo chown -R www-data:www-data /var/www/example.com
    sudo find /var/www/example.com -type d -exec chmod 755 {} +
    sudo find /var/www/example.com -type f -exec chmod 644 {} +
  4. 4Copy the database

    Dump with a consistent snapshot and everything the schema needs, not just the tables:

    on the old host
    mysqldump --single-transaction --quick \
      --routines --triggers --events \
      --default-character-set=utf8mb4 \
      -u olduser -p olddb | gzip > olddb.sql.gz
    • --single-transaction takes the dump inside one transaction, so InnoDB tables are consistent without locking the live site.
    • --routines --triggers --events carry stored procedures, triggers and scheduled events. Leave them off and the schema arrives looking complete while quietly missing behaviour.
    • --default-character-set=utf8mb4 is the mojibake insurance. If the old database is in an older utf8 or a latin1 collation, dump it in its own character set and convert deliberately — never let the client guess.
    on the new server
    sudo mysql -e "CREATE DATABASE newdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
    sudo mysql -e "CREATE USER 'newuser'@'localhost' IDENTIFIED BY 'a-long-random-password';"
    sudo mysql -e "GRANT ALL PRIVILEGES ON newdb.* TO 'newuser'@'localhost'; FLUSH PRIVILEGES;"
    
    gunzip < olddb.sql.gz | mysql -u newuser -p newdb

    Then update the application’s credentials — wp-config.php, .env, configuration.php, whatever your stack calls it. If you would rather click than type, the phpMyAdmin guide covers installing it safely on the new box.

  5. 5Test the new server before DNS points at it

    This is the step that turns a risky migration into a boring one. You can reach the new server by its own IP while the real domain still resolves to the old host.

    The surgical version, one command, affecting nothing else on your machine:

    pretend the domain already moved
    curl --resolve example.com:443:203.0.113.10 -I https://example.com/
    curl --resolve example.com:443:203.0.113.10 -sS https://example.com/ | head

    To click through the site in a browser, add a hosts entry instead — /etc/hosts on Linux and macOS, C:\Windows\System32\drivers\etc\hosts on Windows:

    /etc/hosts
    203.0.113.10   example.com www.example.com

    Now walk the whole site: log in, submit a form, upload a file, run checkout, open the admin area, trigger a password-reset mail. Fix everything you find here, where fixing it costs nothing, and remove the hosts entry when you are done.

  6. 6Cut over

    The last window is short if you prepared. In order: put the old site into maintenance mode (or at least stop writes), run the final delta sync and a fresh database dump, import it, then change the DNS A and AAAA records to the new IP.

    the delta — seconds, not hours, because step 3 already ran
    rsync -azP --delete -e ssh \
      [email protected]:~/public_html/ /var/www/example.com/

    With the TTL at 300, the world follows within minutes. Watch the new server’s access log fill up and the old one go quiet — that is the migration completing, visibly.

  7. 7Issue certificates after DNS, never before

    An HTTP-validated certificate can only be issued once the domain resolves to the server asking for it. That ordering is not negotiable and explains most “certificate warning after migration” reports. Once DNS has moved:

    Let's Encrypt, after the cutover
    sudo certbot --nginx -d example.com -d www.example.com
    # or, on Apache
    sudo certbot --apache -d example.com -d www.example.com

    If you cannot tolerate even a few minutes of warning, issue a DNS-validated certificate ahead of the move instead — that one does not care where the site is hosted yet.

The two weeks after

  • Keep the old account alive. It is your rollback, and it is cheap insurance against the thing nobody tested.
  • Watch the error log daily, not the home page. Missing PHP extensions and broken cron jobs surface in logs long before a user reports them.
  • Re-create the cron jobs from the inventory and check they actually ran. Paths differ on the new server, and a cron job that silently fails is invisible for a month.
  • Set up backups on day one. The old host was quietly doing this for you; on a VPS it is your job now.
  • Harden the boxSSH and a firewall — before you forget that it is now your server and not a managed account.

The failures you are most likely to hit

502 Bad Gateway on a PHP site

nginx is up and PHP-FPM is not, or they disagree about the socket path. The 502 walkthrough has the five-minute diagnosis.

The site loads but every link points at the old domain

Content-managed sites store absolute URLs in the database. For WordPress, replace them with WP-CLI rather than a SQL find-and-replace, because a blind replace corrupts serialised PHP arrays:

dry run first, always
wp search-replace 'http://old.example' 'https://example.com' --all-tables --dry-run
wp search-replace 'http://old.example' 'https://example.com' --all-tables

403 Forbidden, or uploads failing

Ownership arrived wrong, or the directory is not writable by the web server user. Run the chown and find pair from step 3 again, and check the upload directory specifically.

Large uploads fail on the new server but worked before

Shared hosts set generous limits you never saw. On the new box, upload_max_filesize, post_max_size and max_execution_time in the PHP configuration and client_max_body_size in nginx all have to agree, and the smallest one wins.

Was it worth moving?

A VPS gives you the things shared hosting structurally cannot: your own PHP and database versions, background workers and queues, Docker, a real shell, root access to tune anything, and resources that are yours rather than shared with whoever is on the same machine. The cost is that patching, backups and security are now your job.

If that trade is not one you want, the panel route stays open — the cPanel vs Plesk comparison covers choosing between them. And if you are weighing a virtual machine against a whole physical one, VPS vs dedicated server draws the line where it actually falls.

Frequently asked

How do I move a website to a VPS without downtime?

Build and test the new server before DNS points at it: lower the TTL a full old-TTL period ahead, copy files and database, verify through curl --resolve or a hosts entry, then run a final delta sync and switch the records. The visible gap is seconds.

Should I move my email to the VPS too?

Usually not. A new IP has no sending reputation, and a self-run mail server needs correct rDNS, SPF, DKIM, DMARC and ongoing attention. Leave MX at the old host or move mail to a dedicated mail provider and keep it out of the migration.

Why does my site break after moving to a VPS?

Almost always a missing PHP extension, files owned by the wrong user, a database imported in the wrong character set, or absolute URLs still pointing at the old domain. Check 'php -m' against the old host's list first — it is the most common one.

More in Guides

See all