Skip to content
Cloudj

VPS how-to

How to migrate a website to a new VPS with minimal downtime

A step-by-step plan to migrate a website to a VPS: lower DNS TTL, copy files with rsync, move the database, test with a hosts file, then cut over.

Updated · 5 min read

Most website moves go wrong in the same places: DNS that takes a day to update, a database copied while it was still being written to, or a config file that pointed to the old server. This guide gives you a runbook to migrate a website to a VPS with only a few minutes of disruption. It assumes a typical Linux stack (Nginx or Apache, PHP or Node.js, MySQL/MariaDB or PostgreSQL), but the order of steps works for almost any site.

How to migrate a website to a VPS: the plan at a glance

WhenStepSite status
1 to 2 days beforeLower DNS TTL, build the new serverOld server live
Day beforeFirst rsync of files, test database importOld server live
Cutover windowFreeze writes, final sync, DNS switchBrief read-only or maintenance
AfterWatch logs, keep old server for a few daysNew server live

Separating the slow work (copying files, installing software) from the fast work (the final sync) is what keeps downtime short.

Step 1: Lower the DNS TTL early

TTL tells resolvers how long they may cache your DNS records. If your A record has a TTL of 86400 seconds, some visitors could keep reaching the old server for a full day after you change it.

At least one full old-TTL period before the move, set the TTL of the records you will change (usually @ and www) to 300 seconds. Check what resolvers see:

dig +noall +answer example.com A
dig +noall +answer www.example.com A

The second column is the remaining TTL. Once it shows 300 or lower everywhere, you are ready.

Step 2: Build and secure the new server

Pick the location first, because moving again later is extra work. If your visitors are mostly in one region, put the server close to them; the guide on choosing a VPS location by latency and the VPS locations overview explain how to decide. You can measure the difference with the ping and mtr guide before you commit.

Deploy the server from the Cloudj console with an SSH key, then work through the secure new VPS checklist: a non-root user, key-only SSH, a firewall and automatic security updates. Install the same major versions of your web server, language runtime and database as the old server. Matching versions avoids surprises with PHP extensions or database dump formats.

If you plan to build more servers like this one later, Cloudj lets you save your own custom images once the setup is done.

Step 3: First file sync with rsync

Run the first copy while the old site is still live. From the new server, pull files over SSH:

rsync -aHAX --numeric-ids --info=progress2 \
  -e "ssh -p 22" \
  deploy@OLD_SERVER_IP:/var/www/example.com/ /var/www/example.com/

Flags worth knowing: -a keeps permissions and timestamps, -H keeps hard links, -A and -X keep ACLs and extended attributes. The trailing slashes matter: they copy the contents of the folder rather than the folder itself.

Also copy the pieces that live outside the web root:

  • Web server virtual host files (/etc/nginx/sites-available/ or /etc/httpd/conf.d/)
  • TLS certificates, or plan to issue new ones after DNS moves
  • Cron jobs (crontab -l for each user)
  • Environment files and app config with database hosts and credentials

This first sync may take a while for large sites. That is fine; the final sync will only transfer what changed.

Step 4: Rehearse the database move

Do a full test import the day before so you know how long it takes.

For MySQL or MariaDB:

# on the old server
mysqldump --single-transaction --routines --triggers \
  -u root -p app_db | gzip > /tmp/app_db.sql.gz

# on the new server
scp deploy@OLD_SERVER_IP:/tmp/app_db.sql.gz /tmp/
mysql -u root -p -e "CREATE DATABASE app_db CHARACTER SET utf8mb4;"
gunzip < /tmp/app_db.sql.gz | mysql -u root -p app_db

For PostgreSQL:

# on the old server
pg_dump -U postgres -Fc app_db > /tmp/app_db.dump

# on the new server
createdb -U postgres app_db
pg_restore -U postgres -d app_db --no-owner /tmp/app_db.dump

--single-transaction gives a consistent MySQL snapshot of InnoDB tables without locking the site. Note how long the dump and import take; that number is roughly your cutover window. Recreate database users and grants on the new server, since a single-database dump does not include them.

Step 5: Test the new server with a hosts file

Before touching DNS, browse the site on the new server as if DNS already pointed there. Add a line to your own computer's hosts file:

# macOS / Linux: /etc/hosts
# Windows: C:\Windows\System32\drivers\etc\hosts
NEW_SERVER_IP  example.com www.example.com

Or test a single request from the command line without editing anything:

curl -I --resolve example.com:443:NEW_SERVER_IP https://example.com/

Click through the pages that matter: login, checkout or contact forms, file uploads, search, the admin area. Watch the error log while you test (sudo tail -f /var/log/nginx/error.log). Remove the hosts entry when you are done so you do not confuse yourself later.

Step 6: Cut over

Pick a quiet hour. Then, in order:

  1. Freeze writes on the old site. Turn on maintenance mode or make the app read-only so no new orders or comments land on the old database.
  2. Take the final database dump and import it on the new server, exactly as rehearsed.
  3. Run rsync again with the same command. Add --delete only if you are sure the target should mirror the source exactly.
  4. Switch DNS A (and AAAA if you use IPv6) records to the new IP.
  5. Issue or verify TLS certificates on the new server once DNS resolves to it.
  6. Turn off maintenance mode on the new server.

Because the TTL is now 300 seconds, most visitors move within minutes. Some old resolvers ignore TTL, which is why the old server should keep showing a maintenance page rather than accepting writes.

Step 7: Watch, then retire the old server

For the first day, watch the new server's access and error logs and the CPU, RAM and network graphs on its console page. The VPS monitoring guide shows what to look for. Confirm that cron jobs and outgoing email work, since these fail silently.

Keep the old server for a few days in case you need to recover a file. Then raise the DNS TTL back to a normal value and delete the old server. On Cloudj, an hourly server keeps billing while it exists, even when stopped, because its disk and IP stay reserved, so delete it once you are certain you no longer need it.

FAQ

How much downtime should I expect?

For most small and medium sites, the downtime is about as long as the final database dump and import. Rehearsing in Step 4 tells you the real number.

Can I avoid downtime completely?

Read-only or maintenance mode during the final sync is the simple, safe approach. Zero-downtime moves need database replication and more planning, which is rarely worth it for a single site.

Do I need to copy my SSL certificate?

You can, but it is usually simpler to issue a new certificate on the new server once DNS points to it. Copying the old one is useful if you want HTTPS to work during hosts-file testing.

Should I resize the new server or the old one?

Size the new server for your current load with a little headroom. You can resize CPU and RAM later from the console if the graphs show it is needed.

Ready to move

A lower TTL, a rehearsed database import and a hosts-file test turn a risky move into a routine one. Compare sizes on the cloud servers page, check live prices on the pricing page, and deploy your new server in the console when you are ready.

Deploy a server or order proxies

Pick a location in the console, see the live price, and pay from your balance.