A backup has to survive whatever destroys the server, so it must live somewhere else. A good rule is 3-2-1: three copies of your data, on two different kinds of storage, with one copy off site.
Back up three things: website files, databases (as dump files, not copied folders), and configuration. Copy them every night to a different machine or storage service, keep several days of history, and test a restore every few months.
The 3-2-1 rule
A widely used rule of thumb: keep 3 copies of your data, on 2 different kinds of storage, with 1 copy off-site. For a web server, that might be the live server, a nightly copy on a backup server or object storage, and your host’s weekly snapshots. The point is that no single failure, mistake or company can take every copy at once.
What to back up
- Website files, for example
/var/www. - Databases, dumped to a file. Copying the raw database folder while it runs gives a broken copy.
- Configuration:
/etc/nginx,/etc/php, crontabs, and anything else you changed.
1. Dump the databases
sudo mysqldump --all-databases --single-transaction --routines | gzip > /root/backup/db-$(date +%F).sql.gz
--single-transaction takes a consistent copy of InnoDB tables without locking the site.
2. Copy everything to another machine
rsync over SSH only sends what changed, so nightly runs are fast. Set up an SSH key from the server to the backup machine first.
rsync -az --delete /var/www/ backup@backup.example.net:/backups/server1/www/
rsync -az /root/backup/ backup@backup.example.net:/backups/server1/db/
rsync -az /etc/ backup@backup.example.net:/backups/server1/etc/
3. Run it every night
Put the commands in a script, /root/backup.sh, make it executable with chmod +x, and schedule it with sudo crontab -e:
30 3 * * * /root/backup.sh > /var/log/backup.log 2>&1
Delete old local dumps so the disk does not fill:
find /root/backup -name 'db-*.sql.gz' -mtime +7 -delete
A complete backup script
Put it together in /root/backup.sh:
#!/bin/bash
set -euo pipefail
mkdir -p /root/backup
mysqldump --all-databases --single-transaction --routines | gzip > /root/backup/db-$(date +%F).sql.gz
find /root/backup -name 'db-*.sql.gz' -mtime +7 -delete
rsync -az --delete /var/www/ backup@backup.example.net:/backups/server1/www/
rsync -az /root/backup/ backup@backup.example.net:/backups/server1/db/
rsync -az /etc/ backup@backup.example.net:/backups/server1/etc/
set -euo pipefail makes the script stop on the first error instead of carrying on and reporting success. Check /var/log/backup.log the morning after the first run.
4. Keep history, not just a mirror
A plain mirror copies mistakes too: delete a file today and it disappears from the backup tonight. Keep dated copies, or use a tool built for this, such as restic or BorgBackup, which store compressed, deduplicated, encrypted snapshots.
Snapshots with restic
restic sends encrypted, deduplicated snapshots to a backup server or object storage such as S3, Backblaze B2 or similar. After installing it (sudo apt install restic or sudo dnf install restic from EPEL):
export RESTIC_REPOSITORY=sftp:backup@backup.example.net:/backups/server1
export RESTIC_PASSWORD_FILE=/root/.restic-password
restic init
restic backup /var/www /etc /root/backup
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
forget keeps a week of daily snapshots, a month of weekly ones and six monthly ones, and deletes the rest. Store the password somewhere other than the server too: without it, the backups cannot be read.
5. Test a restore
A backup you have never restored is a hope, not a backup. Every few months, restore to a spare server or a local virtual machine and check the site works:
gunzip < db-2026-10-01.sql.gz | mysql
What a restore involves
Write the steps down while things are calm, so you are not working them out during an outage:
- Create a new server with the same operating system and install the web stack.
- Copy the files back:
rsync -az backup@backup.example.net:/backups/server1/www/ /var/www/ - Restore the configuration you need from the
etccopy, such as Nginx sites and PHP pools. Do not copy the whole/etcover a new system. - Import the databases.
- Point DNS at the new server, or test first through your hosts file.
Time it. If a full restore takes four hours, that is your real recovery time, and it is worth knowing before you need it.
Do not rely only on host snapshots
Host snapshots are convenient and fast to restore, but they live with the same company. Keep at least one copy somewhere your host cannot affect.
Monitor your backups
Backups fail silently: a full disk, an expired key, a changed password. Add a check that alerts you when no new backup has arrived. Free “dead man’s switch” services work by expecting a ping at the end of each run, and email you when one is missed:
curl -fsS --retry 3 https://your-check-url >/dev/null
Add that as the last line of the backup script, so it only runs when everything above succeeded.
Related
- RAID levels explained: RAID protects against a failed disk, not deleted files, so it is not a backup.
- How to move a WordPress site to a new host.
- How to set up SSH keys.
Something out of date? Software changes. If a step no longer works, tell us and we will check it and update the page.
