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 |
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):
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:
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:
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:
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:
- Do a final
rsyncand 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). - Update the DNS
A/AAAArecords to the new server's IP. - 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
rsyncandtarfor files,mysqldumpfor databases, andrestic/borgfor 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/hostsbefore 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.