RAID-10 survives a drive failure. It does not survive rm -rf, a bad migration, ransomware, or a legal event. This sets up encrypted, deduplicated backups to a second server in a different jurisdiction — which is what an off-site backup should mean.
The target
A Storage VPS in a country other than the one holding your primary server. If the primary is in Romania, put the backup in Iceland or Panama. Choose a separate failure domain and review the target service price; catalogue locations use different multipliers.
Install and initialise
# On the machine being backed up
apt -y install restic
# Generate a strong repository password and STORE IT SOMEWHERE ELSE
openssl rand -base64 32 > /root/.restic-pass
chmod 600 /root/.restic-pass
export RESTIC_REPOSITORY="sftp:backup@STORAGE_VPS_IP:/srv/backups/node-01"
export RESTIC_PASSWORD_FILE=/root/.restic-pass
restic init
Back up
restic backup /etc /home /var/www /srv \
--exclude-caches \
--exclude '/var/www/*/cache' \
--tag daily
The first run uploads everything. Subsequent transfer volume depends on what changed and on deduplication; measure your own dataset before choosing a transfer allowance.
Automate it
# /etc/systemd/system/restic-backup.service
[Unit]
Description=Restic backup
After=network-online.target
[Service]
Type=oneshot
Environment=RESTIC_REPOSITORY=sftp:backup@STORAGE_VPS_IP:/srv/backups/node-01
Environment=RESTIC_PASSWORD_FILE=/root/.restic-pass
ExecStart=/usr/bin/restic backup /etc /home /var/www /srv --exclude-caches --tag daily
ExecStartPost=/usr/bin/restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
# /etc/systemd/system/restic-backup.timer
[Unit]
Description=Daily restic backup
[Timer]
OnCalendar=*-*-* 03:17:00
RandomizedDelaySec=1800
Persistent=true
[Install]
WantedBy=timers.target
systemctl enable --now restic-backup.timer
systemctl list-timers restic-backup.timer
The odd minute and the randomised delay stop every server you own from hitting the backup target simultaneously.
Verify — this is the step people skip
restic snapshots # list what exists
restic check --read-data-subset=5% # verify integrity of a sample
restic restore latest --target /tmp/test --include /etc/nginx
Restore something, for real, once a quarter. An untested backup is a hypothesis. Put the restore test in your calendar, not in your intentions.
Database dumps
Backing up live database files produces a corrupt copy. Dump first:
# /etc/systemd/system/restic-backup.service — before ExecStart
ExecStartPre=/bin/sh -c 'pg_dumpall -U postgres | zstd > /srv/dumps/pg.sql.zst'
# or
ExecStartPre=/bin/sh -c 'mysqldump --all-databases --single-transaction | zstd > /srv/dumps/mysql.sql.zst'