Guides

3-2-1 backups for a web server: how to implement it properly with restic and Borg

Practical guide to the 3-2-1 rule on Ubuntu: restic and Borg commands, cron scheduling, retention policies and—crucially—how to verify that restore actually works.

UptimeMag editorial team · 14 October 2026 · 4 min read

Cern datacenter

A backup that has never been restored is a gamble, not protection. This guide covers the 3-2-1 rule applied to an Ubuntu web server, with real restic and BorgBackup commands, scheduling via cron, and—the part most people skip—how to verify that restore actually works. Updated 14 October 2026, it remains valid regardless of system version: the basic commands for restic and Borg haven't changed in years.

What is the 3-2-1 rule

The 3-2-1 rule states: 3 copies of data (the original plus two backups), on 2 different storage media or systems, with 1 copy kept offsite. For a typical web server, this means in practice: production files and database, a local backup on a disk separate from the system volume, and a backup on remote storage (another datacentre, an S3-compatible bucket, a storage box) that remains accessible even if the primary server is destroyed.

Taking a provider snapshot is not enough: if the snapshot lives on the same account and provider, it's a single storage medium. Only storage managed by a different vendor or otherwise outside the blast radius of a single incident (hardware, attack, human error, billing suspension) counts as an offsite copy.

restic: repository and first backup

restic is available in Ubuntu repositories and is installed with sudo apt install restic. The central concept is the repository: an encrypted destination where restic saves file revisions.

To create a local repository:

sudo mkdir -p /backup/restic
sudo restic init --repo /backup/restic

The command prompts for a password: that password protects the repository and must be remembered, because it's needed for every subsequent operation. Without the password, nothing can be recovered.

For a remote repository via SFTP, the pattern is the same but the destination changes:

restic -r sftp:user@hostname:/path/to/repo init

as documented in the restic documentation for remote backends.

The actual backup of a folder (e.g. the webroot) is run with:

sudo restic -r /backup/restic backup /var/www

For S3-compatible storage (MinIO, Wasabi, Backblaze B2 via S3) just change the repository string, for example s3:https://endpoint/bucket/repo-name, setting the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables first.

To check repository integrity, not just that it exists:

sudo restic -r /backup/restic check

Borg: init, create, prune

BorgBackup works with archives inside a repository. Initialisation:

borg init --encryption=repokey /path/to/repo

A repository is a filesystem directory containing deduplicated data from zero or more archives.

Backup creates a new archive with a name of your choice (typically date and hostname):

borg create /path/to/repo::Monday ~/src ~/Documents

On a real server the typical pattern includes system directories and exclusions for volatile directories, as shown in Borg's official quickstart script:

borg create --compression lz4 --exclude-caches \
::'{hostname}-{now}' /etc /home /var/www

For remote backend via SSH, the syntax uses the same pattern with user, host and path:

borg init --encryption=repokey ssh://user@host:port/./backups/server1

Retention is managed with prune, which deletes all archives that don't match any of the specified retention options. An example to keep 7 daily copies and 4 weekly ones:

borg prune -v --list --dry-run --keep-daily=7 --keep-weekly=4 /path/to/repo

It's strongly recommended to always run prune with --dry-run first, because prune permanently deletes archives not retained. Note: pruning doesn't immediately free space on the repository; for that you need borg compact after prune.

Scheduling with cron

Both tools are scheduled with a crontab line. For restic, a nightly backup at 2:30 with weekly retention:

30 2 * * * RESTIC_REPOSITORY=/backup/restic RESTIC_PASSWORD_FILE=/etc/restic/password restic backup /var/www && restic forget --keep-daily 7 --keep-weekly 4 --prune

For Borg, the same pattern calls a backup script that includes create and prune in sequence, with BORG_REPO and BORG_PASSPHRASE variables exported at the top of the script, as in the example script in the official documentation. The file should be executable only by the user running the backup (permissions 0700) and the crontab should be set on the user account that has repository access, not root unless necessary.

Testing restore: the part that matters

An untested backup is just a file taking up space. Restore testing should be done at regular intervals, not just once during setup.

With restic, restore a specific snapshot to a separate folder:

restic -r /backup/restic restore <snapshot-id> --target /tmp/restore-test

Before searching for the snapshot-id, list what's available with restic -r /backup/restic snapshots. Alternatively, for a quick check without restoring everything, restic lets you mount the repository as a filesystem and browse files from a past version without extracting them.

With Borg, extracting a specific archive works similarly:

borg extract /path/to/repo::archive-name

run inside an empty test directory, never on production. Borg also supports FUSE mounting of an archive to inspect it without extracting, useful for quickly checking that a single file is recoverable.

Minimal checklist for a backup you can trust

  • Verify that the cron job actually completed successfully (check logs, don't just assume the crontab file exists).
  • Run restic check or borg check on the repository periodically, not just during setup.
  • Do a test restore to a separate directory at least once a month; don't wait for an emergency to discover the password is wrong or the repository is corrupt.
  • Keep the repository password (restic) or passphrase (Borg) somewhere other than the server being backed up: if the server dies and the password was only there, the data is lost anyway.
  • Verify that the offsite copy is on a different account and vendor from the primary server: this is what makes the 3-2-1 rule real and not just words in internal documentation.

Written with the help of artificial intelligence and checked by the editors (EU AI Act, art. 50).