How to Back Up a Server With rsync and restic

Two command-line backup approaches — rsync for plain mirrors, restic for encrypted, deduplicated snapshots to S3 or another server — with a nightly script.

Published
Reading time
4 min

Two tools cover almost every server backup need. rsync makes a mirror: an exact, browsable copy of a directory somewhere else, transferring only what changed. restic makes snapshots: encrypted, deduplicated, versioned, to almost any storage, so you can restore a file as it was on any day. Use rsync when you want simplicity; use restic when you want history and off-site safety. Many servers use both.

rsync: a mirror

bash
rsync -avz --delete /var/www/ backup@203.0.113.50:/srv/backups/web01/www/
  • -a preserves permissions, ownership, timestamps and symlinks.
  • -v shows what it does; -z compresses in transit.
  • --delete removes files at the destination that no longer exist at the source — a true mirror. Leave it off and deleted files accumulate at the destination, which is sometimes what you want.
  • Trailing slashes matter: /var/www/ copies the contents; /var/www copies the directory itself.

Run it from cron nightly and you have a copy of yesterday. Its weakness is that it is only one copy: a file corrupted or encrypted by ransomware on Monday is mirrored on Monday night, and the good version is gone. Two ways round that: keep dated copies using hard links, so unchanged files cost no extra space —

bash
TODAY=/srv/backups/web01/$(date +%F)
YESTERDAY=/srv/backups/web01/$(date -d yesterday +%F)
rsync -a --delete --link-dest="$YESTERDAY" /var/www/ "$TODAY/"

— or use restic, which does this properly.

restic: snapshots

restic stores backups in a repository — a directory, an SFTP server, an S3 bucket, Backblaze B2, or via rclone almost anything — encrypted with a password you choose. Each run creates a snapshot; identical blocks are stored once across all snapshots, so daily backups of a 20 GB site cost 20 GB plus the changes.

Install and initialise:

bash
sudo apt install restic -y          # dnf install restic
export RESTIC_REPOSITORY=s3:s3.eu-west-2.amazonaws.com/my-backups/web01
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export RESTIC_PASSWORD='a-long-passphrase-stored-somewhere-safe'
restic init

Lose the password and the backups are unreadable. Put it in a password manager now.

Back up:

bash
restic backup /var/www /etc /home --exclude='**/cache/**' --exclude='*.log'

Databases need a dump first, because copying live MySQL files gives an inconsistent result:

bash
mysqldump --single-transaction --all-databases | gzip > /var/backups/mysql-all.sql.gz
restic backup /var/backups/mysql-all.sql.gz

Or pipe it straight in with no temporary file: mysqldump ... | restic backup --stdin --stdin-filename mysql-all.sql.

See what you have, and prune old snapshots on a policy:

bash
restic snapshots
restic forget --keep-daily 14 --keep-weekly 8 --keep-monthly 12 --prune

Restore a whole snapshot, or one file as it was on a date:

bash
restic restore latest --target /tmp/restore
restic restore 4f2a9c1b --target /tmp/restore --include /var/www/example.com/wp-config.php

restic mount /mnt/restic presents every snapshot as a browsable directory tree, which is the easiest way to find "the version of that file from last Tuesday".

The nightly script

/usr/local/bin/backup.sh, run from cron at 02:00, with the environment in a root-only file:

bash
#!/bin/bash
set -euo pipefail
source /root/.restic.env       # the RESTIC_* and AWS_* exports, chmod 600

mysqldump --single-transaction --quick --routines --all-databases \
  | restic backup --stdin --stdin-filename mysql-all.sql --tag mysql --quiet

restic backup /var/www /etc /home \
  --exclude='**/cache/**' --exclude='**/node_modules/**' --exclude='*.log' \
  --tag files --quiet

restic forget --keep-daily 14 --keep-weekly 8 --keep-monthly 12 --prune --quiet
restic check --read-data-subset=5% --quiet

curl -fsS -m 10 https://hc-ping.com/your-uuid > /dev/null   # dead man's switch

restic check --read-data-subset verifies a random 5% of the stored data each night, so bit rot at the storage provider is found in weeks, not at restore time. The final curl pings a monitor only on success; if the script fails at any line, set -e stops it before the ping and the monitor alerts you. See knowing when cron jobs fail.

Protecting the repository

The server holds credentials to the bucket, so a compromised server could delete the backups. Use an S3 key with no delete permission for the nightly job (restic's forget --prune then needs to run from elsewhere with a fuller key), or enable object versioning on the bucket so deletions are recoverable. Or run restic's REST server in --append-only mode on a second machine.

Test it

Once a quarter: restore the latest snapshot to a fresh VPS, import the database, load the site. The steps and why they matter are in automatic backups and testing the restore.

On a VPSPioneer managed VPS, this is the pattern we run for you — nightly restic snapshots to off-site storage with 14-day retention and a monitored job — and we will add a second repository under your own account if you want a copy that is entirely yours.

#servers#backups#rsync#restic#linux

Keep reading

More from Servers

All guides

Servers

Cloudways Alternative: Managed Hosting Without the Markup

What Cloudways does, what its management layer is worth, and when shared hosting, a managed VPS or a self-managed VPS fits your sites better.

8 min read →

Servers

Hetzner and DigitalOcean Alternatives for UK & EU Projects

A fair framework for choosing a Hetzner alternative or DigitalOcean alternative in the UK and EU: location, billing, support, backups and how to test.

8 min read →

Servers

MySQL and MariaDB Tuning Basics for a Busy Site

The handful of settings that change MySQL performance — buffer pool, log size, connections, temp tables — how to size them, and how to find slow queries.

4 min read →