Every backup article eventually names its loyalty, and this series has already covered restic, so the honest framing for BorgBackup is as the sibling with different strengths: the same client-side encryption and chunk-level deduplication, the same content-addressed storage that makes incremental history cheap, and three properties that restic does not offer, an append-only repository mode that survives a compromised backup host, a keyfile option that keeps the encryption key off the repository entirely, and a restricted-serve pattern that turns any SSH host into a purpose-built backup target. This article builds the full practice: the repository in its local and remote forms, the encryption modes and their custody tradeoffs, archives and pruning, the integrity checks, the restore scenarios, and the hardened posture that makes the whole system resistant to the attack that comes from the machine running it.
Where Borg sits and the vocabulary shift
Borg organizes a repository into archives, named point-in-time snapshots, where restic's repository holds snapshots as first-class citizens, and the difference is more organizational than architectural: a Borg archive carries a name chosen at creation, hostname-daily-2026-10-14, and the tooling treats the archive as the unit of restore and retention. The underlying mechanics rhyme with the sibling exactly, content-defined chunking splitting files into variable blocks, each chunk encrypted, hashed and stored once, and consecutive archives sharing their unchanged chunks at the same rate restic's snapshots do.
The naming vocabulary translates cleanly across the tools for a reader arriving from the restic article: borg create is the backup command, borg list shows the archives, borg prune applies retention, borg check verifies, and borg extract restores. The conceptual difference that shapes operations is the repository's custody model, and the next section starts there because it is the decision that defines the whole setup.
Repository creation and the encryption modes
The repository lives in a directory, local or remote over SSH, and the init command names both the location and the encryption model:
$ borg init --encryption=repokey /srv/backup/repo
The repokey mode stores the encrypted master key inside the repository's configuration, protected by the passphrase, and the operational property is simplicity: the repository and the passphrase together are the complete recovery kit, and a copy of the repository is a copy of everything except the secret. The keyfile mode makes the opposite trade:
$ borg init --encryption=keyfile /srv/backup/repo
writing the encrypted key to the client's key directory instead, and the repository alone, even with the passphrase, can no longer decrypt anything, the keyfile being a second secret whose custody the runbook names explicitly. The mode choice is a custody decision: repokey suits the standard posture, the passphrase guarded, the repository copied freely, while keyfile suits the hostile-custody posture, the repository stored where the storage operator is not fully trusted, at the cost of managing a second critical artifact.
The remote form of the same command points the path at the SSH destination:
$ borg init --encryption=repokey ssh://
and the remote host needs nothing but Borg installed and SSH access, the simplicity that makes any server a backup target in five minutes. The passphrase travels through BORG_PASSCOMMAND, pointing at a command that prints it, the pattern that connects the secret to the password manager:
export BORG_REPO=ssh://
export BORG_PASSCOMMAND='pass show backup/app01'
Creating archives with compression and patterns
The working backup command names the repository and the archive in the double-colon form:
$ borg create --compression zstd,3 ::app01-2026-10-14 /srv/data /etc/app
and the flags carry the daily decisions. The compression applies to the chunks before storage, zstd as the modern default with a level dialing speed against size, and the compression composes with deduplication the way the restic article described, shared chunks deduplicate identically because the compression is deterministic. The exclusions follow the same double-dash vocabulary, --exclude patterns read from the command line or an --exclude-from file, and the exclusion review belongs to the same runbook discipline the restic article established, every skipped tree a documented decision.
The archive list and its metadata form the working history:
$ borg list
app01-2026-10-14 Tue, 2026-10-14 03:00:01 [/srv/data, /etc/app]
app01-2026-10-13 Mon, 2026-10-13 03:00:01 [/srv/data, /etc/app]
and borg info on a single archive prints the dedup statistics that keep the economics visible, the original size against the deduplicated size, the delta the run actually stored, and the numbers from a healthy daily run are the delta the working set changed, not the total, the property that makes a year of history affordable.
Pruning archives and keeping the list honest
The retention command mirrors restic's policy language exactly in spirit and nearly in spelling:
$ borg prune --keep-daily 7 --keep-weekly 5 --keep-monthly 12
selecting the survivors and deleting the rest, and the companion that reclaims the space is borg compact, the garbage collection that the prune leaves as its own deliberate step, the two-step shape the restic article explained with the same operational reasoning. The weekly maintenance window runs the pair together, and the monthly runs add the next section's check before the compact, the order that keeps the repository verified and tidy in one scheduled pass.
The integrity check and the deep verification
Borg's check command verifies at the depth the runbook chooses. The structural check, borg check, validates the repository's objects and their references without reading every chunk's data, and the deep form adds the data verification:
$ borg check --verify-data
which cryptographically verifies every stored chunk, the defense against bit rot and silent storage corruption, and the schedule that the monitoring reads is the same layered shape the restic article built, the structural check weekly, the data check monthly or quarterly, and the exit code feeding the alerting that keeps the repository's health visible. A check failure on a well-designed setup is a backend event, and the recovery is the archive-level restore onto a fresh repository, the scenario the two-repository posture exists for and the reason the copy of the repository is part of the architecture rather than an afterthought.
Restores, mounts and the working scenarios
The restore command writes an archive's contents back to a directory, and the flexibility lives in the path selection:
$ borg extract ::app01-2026-10-14 --output-dir /tmp/restore srv/data/db.sql
restoring the single file from the full archive, the granularity that the incident workflow needs, while the full-tree restore drops the path filter and rebuilds everything. The mount pattern covers the browsing side:
$ borg mount /mnt/borg
exposing the repository's whole history, each archive a directory under the mount, and the pattern matches the restic mount's role exactly, the incident tool for the single file from the single morning without restoring anything larger.
The three scenarios that exercise the restore loop in practice are worth naming for the runbook. The accidental deletion: the file from Tuesday morning, found in last night's archive, extracted in seconds. The bad deploy: the full tree from the pre-upgrade archive, extracted and compared, the rollback executed with the backed-up copy as the source of truth. The quarterly rehearsal: the full restore onto a clean machine, the application pointed at it, the data verified, the rehearsal's record the only evidence that counts.
The append-only posture and the restricted serve
The property that separates Borg's security story from its sibling's is the append-only repository, the mode in which the repository refuses the operations that delete, the prune and the compact return errors, and the only permitted writes are new archives. The enablement is one configuration value on the repository:
$ borg config --set append_only 1
and the property it buys is protection from the backup host itself: a compromised machine, the one running the nightly borg create, can no longer erase the history, and the retention pruning moves to a different, trusted host, the pattern where the backup server prunes and the client only writes. The deny-from-the-client posture answers the threat that the backup community learned the hard way, the ransomware that reaches the backup tool and prunes before encrypting, and the append-only flag is the one-line defense.
The restricted serve completes the hardened target: the SSH key of the backup client, constrained in the authorized_keys file on the backup host to run only the borg serve command with only the designated repository path, the same authorized-keys discipline the bastion article built, applied to the backup path, and the client that reaches the backup host can do exactly one thing with its access, write to its own repository. The combination of append-only and restricted serve turns the remote backup host into a machine that no single compromised client can harm, the posture that justifies the remote repository as a genuine second copy with genuine independence.
The closing summary
The Borg setup this article built is four commands on a schedule, create nightly, prune and compact weekly, check on the layered cadence, and the restore rehearsal quarterly, with the append-only flag and the restricted serve as the two one-line hardenings that elevate it from a backup to a defended backup. The custody decisions are the runbook's front page, the encryption mode named, the passphrase's location named, the keyfile's location if the hostile-custody mode is chosen, and the restore test dated. The system that keeps all four current is the system whose data survives the host, and that is the entire contract a backup signs.
The cache, the timers and the performance notes
Borg keeps a local cache of chunk indexes under the user's cache directory, and the cache is what makes the nightly create fast: the tool hashes each file, compares against the cached chunk list, and uploads only the difference, without a single round-trip to read the repository's index. The cache is keyed to the repository's identity, the tool refuses a cache built against a different repository as a security feature, and the cache directory on a constrained backup host is the one size that monitoring watches, a multi-terabyte repository's cache running to a few gigabytes over years of operation.
The scheduling through systemd follows the exact pattern the series established everywhere else, the timer unit with the OnCalendar trigger and the service unit sourcing the environment file, and the two operational notes that belong in the runbook are the lock and the serialization: Borg holds a lock on the repository per operation, and the overlapping schedules, a nightly create racing a weekly prune on the same repository, serialize on the lock rather than corrupt, at the price of the later job waiting, which is why the calendar slots are spaced in the mature configurations. The completion monitoring is the exit code and the last-archive age, the same two-signal pattern the restic article wired, and the alert text names the repository and the age, because the on-call's first question is which machine's protection lapsed.
The performance notes close with the three numbers worth knowing on the standard hardware. The create of a daily delta on a warm cache is disk-speed on the read side and network-speed on the upload side, the chunking itself measuring in hundreds of megabytes per second per core. The compact of a large repository is the one long operation, minutes to hours on the multi-terabyte tier, and the schedule respects it. And the deep data check is the longest of all, downloading the full repository for verification, which is why its cadence is quarterly and its window is a weekend, and why the check's completion is itself a monitored event, the three numbers keeping the backup's operational cost honest against the calendar it asks for.