Skip to main content
RunBook Academy

LinuxXLVIII · Backup ToolsBorg Restic

BorgBackup and Restic - the modern deduplicated backup tools

Intermediate⏱ ~12 minborgbackuprestic

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

Not yet marked complete on this device.

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

FeatureBorgBackupRestic
LanguagePythonGo
DeduplicationYes (chunk-based)Yes (chunk-based)
EncryptionYes (AES-OCB)Yes (AES-256)
CompressionYes (lz4, zstd, zlib)Yes (zstd), since 0.14 on repo format v2
SpeedFastVery fast
MaturityMatureMature
S3 backendVia helper (e.g. rclone)Native
Append-onlyServer-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

  1. Q1. Which of these tools is Go-based?

  2. Q2. BorgBackup deduplicates data.

  3. Q3. Which of the following are valid backup commands? Select all that apply.

  4. 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?

  5. 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?

  6. 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.