restic vs BorgBackup: Encrypted, Deduplicated Backups

restic is the better choice for S3 and other object storage. It has native, first-class support for Amazon S3, any S3-compatible store like MinIO or Cloudflare R2, Backblaze B2, Azure and Google Cloud. BorgBackup 1.4 only speaks to local and SSH repositories and needs a tool like rclone or borgmatic to reach object storage, which adds a fragile extra layer.
Yes, both tools encrypt everything client-side before it leaves your machine, including file names and directory structure, so the storage provider learns nothing about your data. restic uses AES-256 with Poly1305 authentication, and Borg uses AES-256 with HMAC-SHA256 in its stable 1.4 series. You only need to protect the passphrase or key file.
Because borg prune only deletes archive entries; it does not reclaim disk space. You must run borg compact as a separate second step after pruning to actually free the space in the repository. restic handles both in one step when you run forget with the prune flag.
It depends on where the repository lives. Borg is consistently faster restoring from local disk or SSH-reachable storage because it reads sequential chunks nearby. restic's rewritten restore engine parallelises chunk downloads, so it performs better when the repository is in object storage across a network.
Yes. A common outdated claim is that restic has no compression, but it has supported zstd compression since version 0.14, released in 2022. You enable it with a repository flag or the RESTIC_COMPRESSION environment variable. BorgBackup has offered lz4, zstd, zlib and lzma compression for years.

Key Takeaway
restic and BorgBackup both give you encrypted, deduplicated backups, but they trade off differently. restic speaks S3, Backblaze B2, Azure and GCS natively and ships a single Go binary. Borg is faster on local and SSH storage, deduplicates finer, and needs a separate compact step to actually free space after pruning.
Every service I run eventually needs a backup story that I actually trust: something encrypted before it leaves the machine, deduplicated so a nightly snapshot does not cost a full copy, and fast enough to restore under pressure. For years the two tools I keep coming back to are restic and BorgBackup. They solve the same problem with different philosophies, and picking the wrong one for your storage target costs you real money and real restore time.
This is the comparison I wish I had before wiring up a VPS backup pipeline. I have run restic against S3-compatible object storage and Borg over SSH to a cheap storage box, and the differences below are the ones that actually mattered in production, not on a benchmark chart.
Both tools store backups in a repository: an encrypted, content-addressed blob store that you initialise once and then append snapshots to. The mental model is nearly identical. You point the tool at a repository location, it splits your files into variable-length chunks, encrypts each chunk, and stores only chunks it has never seen before. A snapshot is just a list of references into that chunk store, which is why the second backup of a mostly-unchanged filesystem is tiny.
# restic: init a repo on S3-compatible object storage
export RESTIC_REPOSITORY="s3:https://s3.example.com/my-backups"
export RESTIC_PASSWORD="a-long-random-passphrase"
restic init
restic backup /srv /etc /var/lib/postgresql
# BorgBackup: init an encrypted repo over SSH, then create an archive
borg init --encryption=repokey-blake2 user@storagebox:/backups/vps
borg create --stats --compression zstd,6 \
user@storagebox:/backups/vps::vps-{now:%Y-%m-%d} \
/srv /etc /var/lib/postgresqlThis is the single biggest deciding factor. restic has native, first-class backends for a long list of targets, so you rarely need glue. Borg was built around local and SSH-reachable repositories and does not speak object storage on its own.
If your backup target is object storage like S3, R2 or B2, reach for restic first. Running Borg against a rclone mount works until the mount silently drops mid-backup and you get a half-written archive. Fewer layers means fewer 3 a.m. surprises.
Both encrypt everything client-side before it touches the backend, including file names and directory structure, so the storage provider learns nothing. restic uses AES-256 in counter mode with Poly1305 authentication and derives keys with scrypt. Borg 1.4 uses AES-256-CTR with HMAC-SHA256; the repokey mode stores the encrypted key inside the repo so you only manage a passphrase. Borg 2.0 modernises this with AES-OCB and chacha20-poly1305 modes.
On deduplication both use content-defined chunking, but Borg tends to use a smaller average chunk size, so it deduplicates slightly finer and often wins by 10 to 15 percent on datasets full of small, partially-changing files. A frequently-repeated claim online is that restic has no compression: that is outdated. restic has supported zstd compression since version 0.14, and you enable it with a repository flag. Borg has offered lz4, zstd, zlib and lzma compression for years.
# restic: enable zstd compression on the whole repo (0.14+)
restic backup --compression max /srv
# or set it once as an environment default
export RESTIC_COMPRESSION=auto
# Borg: choose the compression per-archive
borg create --compression zstd,10 repo::archive /srvRetention policy is where a subtle Borg gotcha lives. Both tools express retention the same friendly way: keep so many daily, weekly, monthly and yearly snapshots and drop the rest. In restic, the forget command with the prune flag both removes the snapshot references and repacks the data, freeing space in one step. In Borg, prune only deletes the archive entries; the disk space is not returned until you separately run borg compact. Skip that and your repository keeps growing even though your retention policy looks correct.
# restic: forget by policy AND reclaim space in one command
restic forget \
--keep-daily 7 --keep-weekly 4 --keep-monthly 6 \
--prune
# Borg: prune by policy, THEN compact to actually free disk
borg prune --list \
--keep-daily=7 --keep-weekly=4 --keep-monthly=6 repo
borg compact repo # do not forget this second stepWhichever tool you pick, always end the backup run with an integrity check on a schedule: restic check --read-data-subset=5% or borg check. Deduplication means one corrupted chunk can poison many snapshots, so verifying that chunks are still readable is not optional.
Restore is the only backup metric that matters when things are on fire. Borg is consistently fast restoring from local or SSH storage because it reads sequential chunks off a nearby disk. restic historically had a slower restorer, but its rewritten restore engine parallelises chunk downloads, which matters enormously when your repository lives in object storage across the network. The honest summary: for local and SSH targets Borg usually restores faster; for object-storage targets restic parallel downloads close the gap and often win because Borg cannot talk to that backend directly anyway.
| Dimension | restic (0.18.x) | BorgBackup (1.4.x) |
|---|---|---|
| Language / distribution | Single static Go binary | Python, installed via package or binary |
| Native backends | Local, SFTP, REST, S3, B2, Azure, GCS, rclone | Local and SSH only (object storage in 2.0 beta) |
| Encryption | AES-256-CTR + Poly1305, scrypt keys | AES-256-CTR + HMAC-SHA256 (OCB in 2.0) |
| Compression | zstd (since 0.14) | lz4, zstd, zlib, lzma |
| Space reclaim after prune | One step: forget --prune | Two steps: prune then compact |
| Best restore target | Object storage (parallel downloads) | Local / SSH storage (sequential speed) |
My rule is simple and based on where the bytes land. If the backup destination is object storage, which for a self-hosted VPS usually means cheap S3-compatible buckets, I use restic: the native backend, single binary, and one-command prune make the whole pipeline trivial to script in a GitHub Actions or cron job. If the destination is a dedicated storage box I reach over SSH, or a second local disk, I use Borg for its finer deduplication and faster local restore, and I never forget the compact step. Both are excellent; the backend decides.
Whatever you choose, test a real restore into a scratch directory at least monthly. A backup you have never restored is a hypothesis, not a backup. Automate the check-and-restore drill the same way you automate the backup itself.