Homelab
Proof

U3 / Backups and restore

restic vs borg vs kopia: measured on 6 GB

Tested on restic 0.19.1

Test result

On this dataset restic made the smallest repository (3.86 GB against 5.01 GB for borg and 5.02 GB for kopia) and restored in 14 s; kopia's default restore took 140 s. All three restored all 42,011 files with matching SHA-256 checksums in every run.

Line drawing: three different storage crates side by side on a bench, each with a stopwatch resting on top.

All three tools did the job without errors, so the choice comes down to repository size, speed and memory on your data. On this test’s data, restic with its default settings produced the smallest repository and the fastest restore. Kopia was fast to back up but slow to restore with default flags. Borg had the lowest peak memory in all three phases (117 MiB, 109 MiB and 104 MiB).

This is one dataset on one virtual machine, so read the numbers as relative behavior, not as what your data will do. The dataset below is deliberately mixed, and one part of it (the compressible files) was built to favor tools that compress by default.

What was measured

Test dataset (seed 20261101)
PartContentSize on disk
small40,000 text files, about 0.1 to 9 KB each0.18 GB
large-compressible5 files of 512 MiB, random bytes mapped to a 16-letter alphabet (4 bits of entropy per byte)2.68 GB
large-random4 files of 512 MiB, incompressible random bytes2.15 GB
duplicates2 byte-identical copies of a 512 MiB file plus copies of 20 small-file directories1.08 GB
Total42011 files, regenerated identically from the seed: yes6.10 GB

The 5% change rewrites 2,000 of the 40,000 small files and overwrites one contiguous 5% region in each of the 9 large files; duplicates/ is untouched.

The dataset is generated from a fixed seed by gen_dataset.py and was regenerated a second time to confirm that the SHA-256 manifest is identical. The 5% change used for the incremental run rewrites 2,000 of the 40,000 small files and overwrites one contiguous 5% region in each of the 9 large files; the duplicates are untouched.

Backup, incremental and restore on a seeded 6 GB dataset (single local repository on the VM's disk)
ToolFirst backupRepo after firstIncremental (5% change)Repo growth from incrementalRestorePeak RSS first backupPeak RSS incrementalPeak RSS restoreRestore checksum check
restic21 s (21-25)3.86 GB (3.86-3.86)7 s (7-7)0.21 GB (0.21-0.22)14 s (13-15)208 MiB (204-211)191 MiB (187-192)195 MiB (190-202)3/3 identical
borg41 s (40-42)5.01 GB (5.01-5.01)17 s (17-18)0.29 GB (0.28-0.30)30 s (29-31)117 MiB (117-117)109 MiB (109-109)104 MiB (100-106)3/3 identical
kopia16 s (15-18)5.02 GB (5.02-5.02)7 s (6-8)0.31 GB (0.31-0.31)140 s (122-168)315 MiB (310-336)161 MiB (160-167)455 MiB (452-499)3/3 identical

Median (min-max) of 3 full cycles, each with a fresh repository, a cold page cache before every timed step and a source tree rebuilt from the same seed. VM 4 vCPU, 3 GB RAM, virtual disk on the host disk. Peak RSS from /usr/bin/time -v. Checksum check: SHA-256 of every restored file compared with the source after the 5% change.

Each of the three cycles used a fresh repository, a rebuilt source tree and a cold page cache before every timed step. Repositories lived on the VM’s own virtual disk, so network and cloud storage behavior is not in these numbers. The tools were installed as restic’s official release binary, borg’s standalone release binary (borg-linux-glibc231-x86_64, not a pip or distribution package) and kopia’s .deb; a different packaging of borg could start and run differently. Peak memory is the resident set size from /usr/bin/time -v, shown in MiB. Restore checksum check means every restored file was hashed and the full manifest (paths and SHA-256) compared with the source after the 5% change. File modes, timestamps and empty directories were not compared.

Defaults recorded

Defaults recorded from each tool's own output
ToolRepository setup usedCompression at defaultEncryption
restic 0.19.1repository format v2 (init default); content-defined chunkingOn: 1.30x compression ratio in `restic stats --mode raw-data` after the two backups (repository format v2)AES-256-CTR + Poly1305-AES (restic docs)
borg 1.4.5init --encryption=repokey-blake2 (mode is mandatory; chosen by the test); fixed chunker settings of borg 1.xlz4 (documented default of borg create)repokey-blake2: AES-based AEAD-style mode with BLAKE2b-256 authentication (borg docs)
kopia 0.23.1splitter DYNAMIC-4M-BUZHASH, hash BLAKE2B-256-128Compression disabled. (global policy output)AES256-GCM-HMAC-SHA256, key derivation scrypt-65536-8-1

Repository setup and kopia values come from raw/*-config.out, raw/*-init.out and raw/*-policy.out; the restic compression ratio is from raw/restic-r1-stats.out; borg compression and the restic and borg encryption wording are from their documentation, linked in the guide. Artifacts: restic official release binary, borg standalone release binary (borg-linux-glibc231-x86_64), kopia .deb package; SHA-256 in raw/binary-sha256.txt.

These are the settings each tool used with no tuning flags. Borg has no default encryption mode, so the test chose repokey-blake2; the borg docs list the modes. The borg create docs give lz4 as the default compression. The restic docs state that new repositories use format version 2, which adds compression support. Kopia’s own output reported “Compression disabled.” for its global policy, and its compression docs describe compression as a policy setting.

What the numbers say

  • Repository size is mostly a compression story here. Restic’s repository (3.86 GB) is smaller than both others on a 6.10 GB source that includes 2.68 GB of low-entropy data. Borg’s default lz4 and kopia’s disabled compression left those files close to their original size. A dataset of already-compressed media would likely narrow the gap; that was not tested.
  • The incremental backup after a 5% change grew the repository by 0.21 GB for restic, 0.29 GB for borg and 0.31 GB for kopia, and took 7 s, 17 s and 7 s.
  • Kopia’s restore (140 s, range 122-168) was the surprise. It was slow in all three cycles (122, 140 and 168 s) and also had the highest peak memory (455 MiB), against 14 s and 195 MiB for restic. The test used kopia’s default restore command and did not try its parallelism options, so this says nothing about a tuned kopia.

What went wrong

The first full attempt of this test failed for kopia on every cycle because the --cache-directory flag was placed on the wrong subcommand; kopia exited with “unknown long flag”. The whole test was then rerun from scratch so that all three tools share one run; the raw output of the failed attempt was not kept, and every number on this page comes from the final run. Nothing else was retried.

The test script

lab/tests/backup-bench/run.sh
Show the full file
#!/usr/bin/env bash
# Test backup-bench: restic 0.19.1 vs borg 1.4.5 vs kopia 0.23.1 on a seeded ~6 GB dataset.
set -uo pipefail
source "$(dirname "$0")/../_common/lib.sh"
ID=backup-bench; NAME=c-$ID; D="$ROOT/lab/tests/$ID"; E="$ROOT/evidence/tests/$ID"; RAW="$E/raw"
rm -rf "$RAW"; mkdir -p "$RAW"
trap 'vm down $NAME' EXIT
host_env "$RAW"
vm up $NAME || exit 1
gssh 'sudo apt-get install -y -qq time bzip2 >/dev/null 2>&1; sudo mkdir -p /srv/work && sudo chown lab:lab /srv/work'
# pinned official release artifacts
gssh 'set -e; cd /tmp
curl -fsSL -o restic.bz2 https://github.com/restic/restic/releases/download/v0.19.1/restic_0.19.1_linux_amd64.bz2 && bunzip2 -f restic.bz2 && sudo install -m755 restic /usr/local/bin/restic
curl -fsSL -o borg https://github.com/borgbackup/borg/releases/download/1.4.5/borg-linux-glibc231-x86_64 && sudo install -m755 borg /usr/local/bin/borg
curl -fsSL -o kopia.deb https://github.com/kopia/kopia/releases/download/v0.23.1/kopia_0.23.1_linux_amd64.deb && sudo dpkg -i kopia.deb >/dev/null; which kopia'
gssh 'sudo ln -sf /usr/bin/kopia /usr/local/bin/kopia; sha256sum /usr/local/bin/restic /usr/local/bin/borg /usr/bin/kopia' > "$RAW/binary-sha256.txt"
vm scp-to "$D/gen_dataset.py" /srv/work/; vm scp-to "$D/apply_change.py" /srv/work/; vm scp-to "$D/guest.sh" /srv/work/
gssh 'bash /srv/work/guest.sh' > "$RAW/guest-stdout.txt" 2>&1
vm scp-from /srv/work/raw/. "$RAW/"
guest_env "$RAW" 'restic version; borg --version; kopia --version; python3 --version'
python3 "$D/summarize.py" "$RAW" "$E"

The guest-side steps are in guest.sh, gen_dataset.py and apply_change.py next to it. The exact commands per tool were:

# restic
restic init -r /srv/repo
restic backup -r /srv/repo /srv/src
restic restore latest -r /srv/repo --target /restore

# borg (encryption mode chosen by the test)
borg init --encryption=repokey-blake2 /srv/repo
borg create --stats /srv/repo::s1 /srv/src
borg extract /srv/repo::s2      # run from inside /restore

# kopia
kopia --config-file=<cfg> repository create filesystem --path /srv/repo --cache-directory=<dir>
kopia --config-file=<cfg> snapshot create /srv/src
kopia --config-file=<cfg> snapshot restore <snapshot-id> /restore/srv-src

Passwords were lab-only-not-a-secret through each tool’s environment variable.

What this test did not cover

  • Network or cloud repositories (S3, SFTP, rclone). Everything ran against a local path on one virtual disk.
  • Other data shapes: millions of tiny files, already-compressed media, databases, very large single files.
  • Tuning: compression levels, kopia restore parallelism, restic pack size, borg chunker parameters. All three ran with defaults except borg’s mandatory encryption choice.
  • Repository maintenance (prune, compact, check), long-running repositories with many snapshots, and behavior after an interrupted backup.
  • Memory under a smaller VM than 3 GB, and on slower disks. Timings come from a VM with 4 vCPU and 3 GB RAM on a shared host; run-to-run ranges are in the table but a busier host would change them.
  • Security properties of the encryption schemes beyond what each project documents.

Sources and how this was made

How this page was made: the commands above were run by an AI agent on the test box on November 1, 2026, and the same agent drafted this text from the saved output. The site's owner reviewed the site before publication. Test method and editorial policy explain how results are checked, and what is not. Spotted an error? Tell us.