All articles
Guides

VPS backups: rsync, restic and the 3-2-1 rule

In short

Back up a VPS by the 3-2-1 rule: three copies of the data, on two different media, one of them off the server and site. Copy data, configs in /etc and database dumps rather than the whole system - the OS reinstalls in minutes. restic is the most convenient tool: it encrypts and deduplicates backups, keeps history by a policy such as 7 daily, 4 weekly and 6 monthly snapshots, and runs nightly from a systemd timer.

Key takeaways
  • The 3-2-1 backup rule: three copies of the data, on two different media, one of them off the main site; a copy on the same VPS does not survive losing the server.
  • A VPS backup covers application data, configs in /etc, home directories and database dumps rather than the whole system: the OS on a VPS reinstalls in a few minutes.
  • Never copy the files of a running database - the copy comes out inconsistent; take a dump first (pg_dump -Fc for PostgreSQL) and back up the dump.
  • restic encrypts and deduplicates backups, keeps a history of snapshots and prunes old ones with restic forget --prune by a policy such as 7 daily, 4 weekly and 6 monthly.
  • A backup only counts as working after a restore test: once a month restore a few files with restic restore and load the database dump into a separate database.

Why keep your own backups if the server is reliable?

Reliable hardware and an SLA protect against a failed disk, not against mistakes and attacks: rm -rf in the wrong directory, a bad upgrade, a break-in, ransomware, a table dropped by accident or an unpaid server. In every one of these cases a copy on the same VPS disappears along with the data. That is why your own off-server copies are always needed - no matter where the server runs or what the provider promises.

What is the 3-2-1 rule?

The 3-2-1 rule is the classic backup scheme: 3 copies of the data (the live one and two backups), on 2 different media or systems, with 1 copy somewhere else. For a VPS it looks like this:

CopyWhere it livesWhat it protects against
Live dataThe VPS itselfNothing - it is the original
Backup 1A second server in another locationLoss, compromise or deletion of the main VPS
Backup 2S3 object storage or a home computerTrouble with both servers at once

Step 1. Decide what to back up

  • /etc - configs for nginx, systemd, SSH and the firewall: setting them up again takes hours;
  • application data - /home, /opt, /srv, /var/www or wherever your projects live, including .env files;
  • database dumps - PostgreSQL, MySQL, SQLite, not the files of a running database;
  • Docker volumes (/var/lib/docker/volumes) if data lives in containers - again, preferably via a database dump;
  • there is no need to copy the whole system: /usr, /var/cache, /proc, /tmp and node_modules come back by installing the OS and packages.

Step 2. Dump databases before the backup

Database files copied while the database runs are almost certainly inconsistent, and the database will not start from them. A dump is taken from the running database without stopping it and is always consistent. An example for PostgreSQL - more in the guide to PostgreSQL on a VPS:

bash
sudo mkdir -p /var/backups/db
sudo -u postgres pg_dump -Fc myapp | sudo tee /var/backups/db/myapp.dump > /dev/null
sudo ls -lh /var/backups/db

Step 3. Set up SSH key login to the backup server

Scheduled backups run without a password, so they need a dedicated SSH key for root. ssh-copy-id asks for the backup user's password on the second server once, and the entry in /root/.ssh/config gives it the short name backup-server. More on keys in the guide to connecting over SSH:

bash
sudo ssh-keygen -t ed25519 -N "" -f /root/.ssh/backup_key
sudo ssh-copy-id -i /root/.ssh/backup_key.pub backup@203.0.113.20
sudo tee -a /root/.ssh/config > /dev/null <<'EOF'
Host backup-server
    HostName 203.0.113.20
    User backup
    IdentityFile /root/.ssh/backup_key
EOF

Step 4. The simple option: rsync to another server

rsync copies only changed files, so a repeat run takes seconds. -a preserves permissions and timestamps, and --delete removes files from the copy that no longer exist on the server. rsync has to be installed on both servers:

bash
sudo apt install -y rsync
sudo rsync -a --delete /etc /home /opt /var/backups backup-server:/srv/backup/myvps/

Step 5. restic: encryption, deduplication and history

restic stores backups as snapshots: each snapshot is the full state at the time of the run, but identical parts of files are stored once (deduplication), so 30 daily snapshots of slowly changing data take little more space than one. Everything is encrypted on your server, and the backup server sees only encrypted blobs. The SFTP repository uses the key from step 3:

bash
sudo apt install -y restic
sudo sh -c 'openssl rand -base64 32 > /root/.restic-password && chmod 600 /root/.restic-password'
sudo -i
export RESTIC_REPOSITORY=sftp:backup-server:/srv/restic/myvps
export RESTIC_PASSWORD_FILE=/root/.restic-password
restic init

The first snapshot, the snapshot list, pruning by retention policy and a repository integrity check. forget --prune keeps the last 7 daily, 4 weekly and 6 monthly snapshots and deletes data no snapshot references any more:

bash
restic backup /etc /home /opt /var/backups --exclude-caches
restic snapshots
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
restic check

Besides SFTP, restic writes to S3-compatible storage - a convenient place for the second backup under the 3-2-1 rule. The list of supported backends is in the documentation at restic.net.

Step 6. Run backups on a schedule

Put the dump and restic into one script: set -eu stops it at the first error, so a broken dump is never recorded as a successful backup:

bash
sudo tee /usr/local/sbin/backup.sh > /dev/null <<'EOF'
#!/bin/sh
set -eu
export RESTIC_REPOSITORY=sftp:backup-server:/srv/restic/myvps
export RESTIC_PASSWORD_FILE=/root/.restic-password

# database dump - if the server runs PostgreSQL
mkdir -p /var/backups/db
runuser -u postgres -- pg_dump -Fc myapp > /var/backups/db/myapp.dump

restic backup /etc /home /opt /var/backups --exclude-caches
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
EOF
sudo chmod 700 /usr/local/sbin/backup.sh

The systemd timer runs the script every night at 03:30 with a random delay of up to 15 minutes, and Persistent=true catches up on a missed run if the server was off at the time. Each run's result is visible in journalctl -u backup:

bash
sudo tee /etc/systemd/system/backup.service > /dev/null <<'EOF'
[Unit]
Description=Restic backup
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/backup.sh
EOF
sudo tee /etc/systemd/system/backup.timer > /dev/null <<'EOF'
[Unit]
Description=Daily restic backup

[Timer]
OnCalendar=*-*-* 03:30:00
RandomizedDelaySec=15m
Persistent=true

[Install]
WantedBy=timers.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers backup.timer

If you prefer cron, the same job is one line in sudo crontab -e:

crontab
30 3 * * * /usr/local/sbin/backup.sh >> /var/log/backup.log 2>&1

Step 7. Test the restore

A backup that has never been restored is a hope, not a backup. Once a month restore a few files into a temporary directory and compare them with the originals, and load the database dump into a separate database with pg_restore:

bash
restic snapshots
restic restore latest --target /root/restore-test --include /etc/nginx
diff -r /etc/nginx /root/restore-test/etc/nginx && echo OK

Time the whole thing while you are at it: how long it takes to bring everything up on a clean server - install the OS and packages, restore /etc, the data and the database. Write that recovery plan down before an outage, not during one.

Where to store backups

StorageProsCons
A second VPS in another countryFull control, rsync and restic over SFTP just workOne more server to maintain
S3 object storageNothing to administer, pay for the volumeFees for storage and downloads
A home computer or NASFree, the copy is physically yoursDepends on home internet and power

Tihost has three locations - Germany, Finland and Poland - so the backup server can sit in a different country from the main one: an outage at one data center will not hit both copies. See the guide to choosing a VPS location. Starter configurations work well as backup storage - the largest of them has the biggest disk:

ConfigurationGermanyFinlandPoland
1 vCPU·2 GB RAM·40 GB NVMe$4.00$4.00$5.00
2 vCPU·4 GB RAM·80 GB NVMe$7.70$7.70$9.60
4 vCPU·4 GB RAM·140 GB NVMe$12.30$12.30$15.40
Launch a server in 2 minutes

AMD Ryzen 9, NVMe and DDoS protection in Germany, Finland and Poland. Pay with crypto or card.

Order a Server

FAQ

What is the 3-2-1 backup rule?

The 3-2-1 rule means three copies of the data, on two different media or systems, with one of them off the main site. For a VPS that is the server itself, a copy on a second server in another location and one more in object storage or at home.

Why is restic better than rsync for backups?

rsync with --delete maintains a mirror, so a corrupted or deleted file also disappears from the copy on the next run. restic keeps a history of snapshots, encrypts and deduplicates the data, so you can go back to the state from a week ago.

Do I need to back up the entire VPS?

No. Backing up /etc, application data, home directories and database dumps is enough. The OS and packages are faster to reinstall, and a full-system copy takes more space and moves to another server less cleanly.

Can I just copy the database directory?

Copying the files of a running database produces an inconsistent copy. Take a dump first - pg_dump -Fc for PostgreSQL or mysqldump for MySQL - and back up the dump file.

How often should I back up a VPS?

For most projects a nightly backup with 7 daily, 4 weekly and 6 monthly snapshots kept is enough. If losing a day of data is unacceptable, back up the database more often - for example every hour.

What happens if I lose the restic repository password?

Without the password a restic repository cannot be decrypted, and every backup in it is useless. Keep the password in a password manager as well as on the server.