LinuxXLVIII · Backup ToolsBorg Restic
BorgBackup and Restic - the modern deduplicated backup tools
What you'll learn
- Use BorgBackup for deduplicated, encrypted backups
- Use Restic for simple, modern backups
- Choose between BorgBackup and Restic
- Set up scheduled backups with retention
- Escrow the repository key offline so an encrypted backup stays recoverable
- Run a non-interactive encrypted backup under cron and alert on failure
- Reclaim space with borg compact and verify integrity with borg check --verify-data
Prerequisites
Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-09
BorgBackup and Restic are the modern, deduplicated, encrypted backup tools. They support snapshots, encryption, and efficient storage. This lesson covers both.
BorgBackup
BorgBackup (Borg) is a deduplicating backup program with optional compression and authenticated encryption.
Init
borg init --encryption repokey-blake2 /backup/repo
--encryption repokey-blake2: AES-OCB + BLAKE2b. Strong authenticated encryption./backup/repo: the repository location (local disk, NFS, SSH, S3 with helper).
Escrow the key before you do anything else
With repokey the key material is stored inside the
repository and is protected by the passphrase. If the
passphrase is lost, every archive in that repository is
permanently unreadable. There is no recovery path, no
support ticket, no vendor override.
Export the key the moment the repository is created:
# Machine-readable export - store offline
borg key export /backup/repo /root/borg-key-backup.txt
# Printable form for a physical safe or paper escrow
borg key export --paper /backup/repo /root/borg-key-paper.txt
# Prove the export actually works before you rely on it
borg key import /backup/repo /root/borg-key-backup.txt
Then move both files off the host. Restic has the
equivalent: restic key list enumerates the keys on a
repository, and restic key add creates a second
passphrase so no single lost secret locks you out.
Create backup
borg create /backup/repo::backup-{now} /var/data
{now} is a placeholder for the current timestamp. The
backup is automatically compressed and deduplicated.
List and inspect
borg list /backup/repo
borg info /backup/repo
borg list /backup/repo::backup-2026-08-09
Restore
# List contents
borg list /backup/repo::backup-2026-08-09
# Extract
borg extract /backup/repo::backup-2026-08-09
# Extract specific path
borg extract /backup/repo::backup-2026-08-09 path/to/file
Prune, then compact
# Keep 7 daily, 4 weekly, 6 monthly
borg prune --keep-daily=7 --keep-weekly=4 --keep-monthly=6 /backup/repo
# Actually free the space
borg compact /backup/repo
Prune removes old archives per the retention policy. The backup data that is still referenced by retained archives is preserved.
Since Borg 1.2, prune only deletes the archive
references. The underlying segment files are left in
place and no disk space is returned until borg compact
runs. A schedule that prunes and never compacts looks
correct - borg list shows the right retention - while the
repository grows monotonically until the backup volume
fills and borg create starts failing on ENOSPC.
Always pair them. Restic’s equivalent is
restic forget --prune, where --prune performs the same
reclaim step in one command.
Verify repository integrity
Prune and compact do not detect corruption. A bit rot on the backup volume, a truncated segment from a failed write, a silently failing disk - none of these surface until someone tries to restore.
# Full read and verify of all chunk data (slow, schedule weekly)
borg check --verify-data /backup/repo
# Restic equivalent
restic -r /backup/repo check --read-data
Without --verify-data, borg check validates only the
repository structure and archive metadata, not the chunk
contents. Without --read-data, restic check does the
same. These are the only commands that find silent
corruption, they must be scheduled, and their exit status
must be alerted on.
Restic
Restic is a simpler, Go-based backup program with similar features.
Init
restic init --repo /backup/repo
Backup
restic -r /backup/repo backup /var/data
List and restore
restic -r /backup/repo snapshots
restic -r /backup/repo restore latest --target /restore
BorgBackup vs Restic
| Feature | BorgBackup | Restic |
|---|---|---|
| Language | Python | Go |
| Deduplication | Yes (chunk-based) | Yes (chunk-based) |
| Encryption | Yes (AES-OCB) | Yes (AES-256) |
| Compression | Yes (lz4, zstd, zlib) | Yes (zstd), since 0.14 on repo format v2 |
| Speed | Fast | Very fast |
| Maturity | Mature | Mature |
| S3 backend | Via helper (e.g. rclone) | Native |
| Append-only | Server-enforced (borg serve --append-only) | No repository mode; backend-enforced only |
For most production: BorgBackup is more flexible; Restic is simpler and has native S3 support. Compression is no longer a reason to pick one over the other — do not let an older comparison decide it for you.
Scheduled backup
An encrypted repository prompts for its passphrase on stdin. Under cron there is no terminal, so the naive entry below either fails immediately or blocks forever - and because nothing checks the exit status, nobody finds out until a restore is attempted:
# WRONG - no passphrase, no compact, no alert
0 2 * * * root borg create /backup/repo::backup-{now} /var/data && \
borg prune --keep-daily=7 --keep-weekly=4 --keep-monthly=6 /backup/repo
Three defects: the job cannot authenticate, it never compacts so the repository grows without bound, and a failure produces no signal at all.
# /etc/cron.d/borg
BORG_PASSCOMMAND="cat /root/.borg-passphrase" # root-owned, mode 0400
BORG_REPO=/backup/repo
0 2 * * * root borg create --stats --compression zstd ::backup-{now} /var/data \
&& borg prune --keep-daily=7 --keep-weekly=4 --keep-monthly=6 \
&& borg compact \
|| logger -t borg -p user.err "borg backup FAILED on $(hostname)"
# Integrity verification - prune and compact do NOT detect corruption
0 4 * * 0 root borg check --verify-data \
|| logger -t borg -p user.err "borg check FAILED on $(hostname)"
BORG_PASSCOMMAND runs a command to fetch the passphrase,
which keeps the secret out of the crontab and out of the
process table. BORG_PASSPHRASE also exists but puts the
plaintext in the environment - prefer BORG_PASSCOMMAND,
or read the secret from your agent or vault client.
The || logger arm is what makes the job observable. Route
user.err from tag borg to your alerting pipeline, and
alert on absence too: a job that never runs emits no
error.
Knowledge check
Knowledge check · 6 questions
Q1. Which of these tools is Go-based?
Q2. BorgBackup deduplicates data.
Q3. Which of the following are valid backup commands? Select all that apply.
Q4. A nightly job runs borg create then borg prune with a 7-daily/4-weekly/6-monthly policy. borg list shows exactly the expected archives, yet df on /backup has gone from 40% to 96% over three months. What is happening?
Q5. The primary site is destroyed, including the self-hosted secrets vault. Immutable offsite Borg backups (repokey-blake2) are intact and reachable. What determines whether you recover?
Q6. Detecting a corrupted chunk in a Borg repository requires borg check --verify-data; prune and compact will not find it.
Passing score: 75%. Answers are checked in this browser.