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

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 checkorborg checkon 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).