Skip to content

Hosting

Backups and Site Migration

Two of the most important — and most often botched — jobs in hosting are keeping good backups and moving a site to a new server without breaking it. They share the same skills: copying files, dumping databases, and verifying that what you copied actually works. This page covers a practical backup strategy and a step-by-step, low-downtime migration with a rollback plan.

Tested on

AlmaLinux 9 / RHEL 9. The tools (rsync, tar, mysqldump, restic/borg) are identical on Debian/Ubuntu; package names are noted inline.

Part 1: Backups

What to back up

A hosted site is more than its HTML. Capture all four pieces or your "backup" won't actually restore a working site:

Component Where it usually lives Tool
Website files /var/www/<site>, document root rsync, tar
Databases /var/lib/mysql (dump it, don't copy it) mysqldump
Mail Maildir under /var/mail or /home/*/Maildir rsync, tar
Configuration /etc — web-server vhosts, PHP, TLS certs, DNS, cron rsync, tar

See Databases for hosting for the database side and Email hosting for mail.

The 3-2-1 rule

The industry rule of thumb for surviving disasters:

  • 3 copies of your data (the live one plus two backups),
  • on 2 different types of media or storage,
  • with 1 copy kept off-site (different building / cloud / region).

A nightly dump sitting on the same server as the live data is not a backup — one failed disk or one ransomware event takes both. Get at least one copy off the box.

Tools

rsync — efficient file copying that only transfers changed blocks, ideal for repeated backups and for migration:

# Mirror the document root to a backup host over SSH
rsync -avz --delete /var/www/mysite/ [email protected]:/backups/mysite/

-a preserves permissions/ownership/timestamps, -v is verbose, -z compresses in transit, and --delete makes the destination an exact mirror (omit it if you want to keep deleted files).

tar — bundle a directory into a single timestamped, compressed archive (good for point-in-time snapshots):

tar -czf /backup/mysite-$(date +%F).tar.gz -C /var/www mysite

mysqldump — the database backup, covered in Databases for hosting:

mysqldump -u root -p --single-transaction --quick mysite_db | gzip > /backup/mysite_db-$(date +%F).sql.gz

restic / borg — for serious backups, use a deduplicated, encrypted, incremental backup tool rather than piling up tar files. Both store only changed chunks (so 30 daily snapshots cost far less than 30 full copies), encrypt at rest (safe to push to cloud/object storage), and support retention/pruning policies. Example with restic to an S3-compatible bucket:

export RESTIC_REPOSITORY="s3:https://s3.example.com/mybucket"
export RESTIC_PASSWORD="a-strong-repo-passphrase"
restic backup /var/www /etc /backup/db-dumps
restic forget --keep-daily 7 --keep-weekly 4 --prune

Install with sudo dnf install -y restic (RHEL) or sudo apt install -y restic (Debian); borgbackup is packaged similarly.

Control-panel backups — cPanel, Plesk, and similar (see Control panels) generate full account archives (files + database + mail + DNS zone) you can download or push to remote storage. Convenient and restore-aware, but verify they're actually leaving the server.

VM / hypervisor snapshots — a snapshot of the whole virtual machine is great for fast whole-server rollback before risky changes. But a snapshot is not an application-consistent database backup and usually lives on the same storage, so treat it as a complement to file/DB backups, not a replacement.

Automate on a schedule

Manual backups get forgotten. Put a backup script on a timer so it runs every night without anyone touching it:

#!/usr/bin/env bash
# /usr/local/sbin/site-backup.sh
set -euo pipefail
STAMP=$(date +%F)
mysqldump -u root --single-transaction --quick mysite_db | gzip > "/backup/mysite_db-${STAMP}.sql.gz"
tar -czf "/backup/mysite-files-${STAMP}.tar.gz" -C /var/www mysite
rsync -az --delete /backup/ [email protected]:/offsite/mysite/

Schedule it with cron or a systemd timer and review the logs — see Cron and timers. Add log rotation and a retention policy so old backups are pruned automatically.

Test your restores

A backup you have never restored is not a backup

The only way to know a backup works is to restore it and confirm the site runs. Backups silently break all the time — wrong path, empty dump, expired credentials, a cron job that stopped months ago.

On a schedule (monthly is reasonable) restore the latest backup onto a scratch VM or directory, import the database, point a browser at it, and confirm pages and logins work. Then you know you can recover, instead of hoping.

Part 2: Site migration

Moving a site to a new server is essentially "make a backup on server A, restore it on server B, then switch DNS." Done carefully it has near-zero downtime; done carelessly it loses orders placed during the move. Plan for both servers to run in parallel until you're confident.

Step 1 — Sync the files

Copy the document root (and anything outside it the app needs, like upload dirs) to the new server:

rsync -avz /var/www/mysite/ root@NEW_SERVER_IP:/var/www/mysite/

Run it once now, and again right before cutover to catch last-minute changes.

Step 2 — Dump and import the database

On the old server:

mysqldump -u root -p --single-transaction --quick mysite_db | gzip > /tmp/mysite_db.sql.gz
scp /tmp/mysite_db.sql.gz root@NEW_SERVER_IP:/tmp/

On the new server, create the database and user (see Databases for hosting), then import:

gunzip < /tmp/mysite_db.sql.gz | mysql -u root -p mysite_db

Update the app's config (DB host/user/password) on the new server to match the new database credentials.

Step 3 — Recreate the web-server config

Copy or re-author the virtual host / server block on the new machine — document root, server name, PHP-FPM socket, and TLS — then reload. See Virtual hosts and PHP and PHP-FPM. Re-issue or copy the TLS certificate too (HTTPS with Let's Encrypt).

Step 4 — Test on the new server BEFORE cutover

This is the step that prevents disasters. Test the new server while DNS still points at the old one by faking the lookup on your own machine. Add a line to your local /etc/hosts mapping the domain to the new IP:

203.0.113.50   www.mysite.com mysite.com

Now your browser resolves mysite.com to the new server only for you, while the rest of the world still hits the old one. Click through the site, log in, submit a form, check HTTPS. (You can also test via the raw IP or a temporary hostname.) Remove the /etc/hosts line when done.

Tip

Only cut over once the new server passes this test. Never switch DNS to a server you haven't loaded in a browser.

Step 5 — Lower the DNS TTL beforehand

DNS records are cached for their TTL (time to live). If your record's TTL is 24 hours, visitors may keep hitting the old server for a full day after you change it. So, a day or two before the migration, lower the TTL on the relevant records (e.g. from 86400 to 300 seconds). See Domains and DNS. After the cutover succeeds you can raise it again.

Step 6 — Cut over by updating DNS

With everything tested and the TTL low:

  1. Do a final rsync and a fresh database dump/import to capture last-minute changes (consider putting the old site in read-only/maintenance mode for the few minutes this takes, so no new orders are lost).
  2. Update the DNS A/AAAA records to the new server's IP.
  3. Watch the new server's access log fill up and the old one's go quiet as caches expire.

Because the TTL is low, the world moves over within minutes instead of a day.

Step 7 — Rollback plan

Have an escape route before you start:

  • Keep the old server running and untouched for at least the TTL period plus a margin (a few days is safer) — do not delete it.
  • If the new server misbehaves, revert the DNS records back to the old IP. Since the TTL is still low, traffic returns quickly.
  • Only after the new server has run cleanly for a couple of days should you raise the TTL back up and decommission the old box.

Minimize and verify

Two rules keep migrations safe: minimize downtime (low TTL + parallel running means seconds, not hours) and verify (test on the new host before cutover, watch logs after, keep the rollback ready). Skipping either is how migrations turn into outages.

Verify your work

# Backups exist and are recent and non-empty
ls -lh /backup/ | tail

# The latest DB dump is real SQL, not an error message
zcat /backup/mysite_db-*.sql.gz | head -n 20

# The backup timer/cron is actually scheduled and ran
systemctl list-timers | grep -i backup     # if using a systemd timer
sudo crontab -l                             # if using cron

# (Migration) The new server serves the site when you fake DNS via /etc/hosts
curl -I --resolve www.mysite.com:443:203.0.113.50 https://www.mysite.com/

You should see recent, non-empty backup files, valid SQL at the top of the dump, a scheduled job, and a 200 OK from the new server before you ever touch real DNS.

Summary

  • Back up all four parts of a site: files, databases, mail, and configuration.
  • Follow 3-2-1: 3 copies, 2 media, 1 off-site — a dump on the same server is not a backup.
  • Use rsync and tar for files, mysqldump for databases, and restic/borg for deduplicated, encrypted, retained backups; control-panel archives and VM snapshots complement (not replace) these.
  • Automate backups on a schedule and test restores regularly — an untested backup is just a hope.
  • Migrate by syncing files, dumping/importing the database, recreating the vhost, and testing on the new server via /etc/hosts before cutover.
  • Lower the DNS TTL beforehand, cut over by changing DNS, keep the old server as rollback, and minimize downtime by running both in parallel.

Test yourself