Kopia: The Backup Tool That Deduplicates Everything

Content-addressed storage, a proper GUI, and a policy engine that does the remembering for you

Contents

I ran Restic for two years before I looked seriously at Kopia, and the thing that finally moved me was not a benchmark, it was a support ticket I never had to write. A snapshot policy had quietly evolved over months — different retention on different machines, different exclude lists nobody remembered setting — and when I went looking for what was actually configured, Restic had no answer beyond “whatever flags you happened to type last time.” Kopia keeps that configuration as a first-class, inspectable object. That single difference changed how I think about backup tooling: the policy is the product, not the command line.

Kopia is a fast, cross-platform backup tool built around content-addressed storage. It splits files into variable-size chunks, hashes each one, and stores each unique chunk exactly once across every snapshot you ever take, on any machine sharing that repository. It speaks a long list of backends natively, ships a genuinely usable web GUI alongside its CLI, and its policy engine lets you define retention and behaviour once, at a repository or path level, rather than re-typing flags on every invocation. This post covers what makes it different, a worked setup, and where it still falls short of Borg or Restic.

Why content-addressed deduplication matters

Advertisement

Traditional incremental backups compare a file’s modification time or size against the last run and copy the whole file if anything changed. Move a 4 GB video file to a different folder, or restore an old copy over a new one, and most tools back it up again in full, because to them it looks new. Content-addressed storage does not care where a chunk of data lives or what it is called — it only cares what the bytes hash to. Rename a directory, split a monolithic backup source into two, restore last month’s data over this month’s: if the underlying chunks already exist in the repository, nothing new is written.

This matters enormously for a homelab. My media library, my container volumes and my laptop’s home directory all get backed up to the same Kopia repository, and any chunk that happens to be identical across those three sources — a shared base image layer, a duplicated ISO, an identical config file — is stored once. Borg and Restic both deduplicate too, but each maintains its own definition of a repository tied fairly closely to a single machine’s typical workflow; Kopia was designed from day one for many machines sharing one repository, with per-source policies layered on top.

The mechanism underneath is content-defined chunking, which is a more subtle idea than it first sounds. A naive approach would slice every file into fixed-size blocks — say, 4 MB each — and hash those. That works fine until you insert a single byte near the start of a large file: every block boundary after that point shifts by one byte, none of the old block hashes match any more, and the whole file gets re-uploaded despite being almost identical to what was already stored. Kopia avoids this with a rolling hash that decides chunk boundaries based on the content itself, so a boundary tends to land in the same place in the byte stream whether or not something changed earlier in the file. Insert a paragraph into a text file, or prepend a new frame to the front of a video, and only the chunks actually touching the change end up different; everything downstream of the edit still matches what was already in the repository. That property is what makes deduplication genuinely useful for backup sources that mutate in place — log files, database dumps, container image layers — rather than only for files that are either byte-identical or completely different.

It also explains why deduplication ratios vary so much between sources. A directory of already-compressed media (JPEGs, most video codecs, anything already zstd- or gzip-compressed) deduplicates poorly no matter which tool you use, because compression deliberately removes the redundancy content-defined chunking relies on finding. A directory of container images, VM disk images, or plain-text configuration deduplicates extremely well, often reducing effective storage by more than half once several similar sources share a repository. Knowing which of your data falls into which category is worth five minutes of kopia snapshot create --json inspection before you assume a number from someone else’s benchmark applies to your own array.

Installing and initialising a repository

Kopia ships static binaries and packages for Linux, macOS and Windows. On Debian/Ubuntu:

1
2
3
4
curl -s https://kopia.io/signing-key | sudo gpg --dearmor -o /usr/share/keyrings/kopia-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/kopia-keyring.gpg] https://packages.kopia.io/apt/ stable main" \
  | sudo tee /etc/apt/sources.list.d/kopia.list
sudo apt update && sudo apt install kopia

A repository can live on local disk, SFTP, or any of the object-store backends: S3 and S3-compatible stores, Backblaze B2, Azure Blob, Google Cloud Storage, and a self-hosted Kopia repository server for multi-client setups. Here it is against an S3-compatible bucket:

1
2
3
4
5
6
kopia repository create s3 \
  --bucket=homelab-backups \
  --endpoint=s3.eu-central-1.example.com \
  --access-key="$AWS_ACCESS_KEY_ID" \
  --secret-access-key="$AWS_SECRET_ACCESS_KEY" \
  --password="a-long-strong-passphrase"

Everything is encrypted client-side with AES-256-GCM before it ever leaves the machine, keyed from that passphrase — lose it and the repository is unrecoverable, same as Borg or Restic. Compression defaults to zstd-fastest; for a homelab with CPU to spare, kopia repository set-parameters --compression=zstd trades a little CPU for a smaller repository.

Treat that passphrase with the same seriousness you’d give a LUKS unlock key, because functionally it is one: nobody, including Kopia’s own maintainers, can recover a repository without it. I keep mine in a password manager with an offline export printed and stored away from the house, the same discipline I’d apply to any single secret that gates access to years of backups. It’s worth writing the passphrase down somewhere that survives the loss of every machine that currently has it cached, precisely because the failure scenario where you need the backup most — the primary machine is gone — is also the scenario where a passphrase only ever typed into that machine’s terminal history is gone with it. A repository you can’t unlock is indistinguishable, in practice, from a repository that was never created.

Snapshots and policies

Advertisement

A snapshot is created with kopia snapshot create, but the interesting part is that repeated runs are driven by policy rather than flags:

1
kopia snapshot create /etc /home /var/www

Policies attach to a path (or globally) and cover retention, scheduling, exclusions and compression, and they are inherited — set a sane default once and every new source picks it up automatically:

1
2
3
4
kopia policy set /home \
  --keep-daily=7 --keep-weekly=4 --keep-monthly=6 --keep-annual=2 \
  --exclude="**/.cache" --exclude="**/node_modules" \
  --compression=zstd

kopia policy show /home prints the effective policy after inheritance, which is the answer to the question that started this whole switch for me: what, exactly, is configured right now. Run kopia policy list to see every policy in the repository at a glance, something I never had a clean equivalent for in Restic without grepping shell history.

The server and web UI

The part that actually got my household on board is kopia server. It runs a local HTTPS server backed by the repository and serves a browser UI for browsing snapshots, restoring files, and watching jobs run:

1
2
3
4
kopia server start \
  --address=https://0.0.0.0:51515 \
  --tls-generate-cert \
  --server-username=admin --server-password="a-console-only-password"

Point a browser at https://mylab.local:51515, authenticate, and you get a file tree of every snapshot with a restore button — no CLI required for the person who just wants their photos back. Multiple machines can also connect to a shared kopia server as clients, each with scoped credentials, which is the multi-machine story Borg and Restic don’t offer out of the box without extra plumbing.

Compared to Borg and Restic

If you already run Borg or Restic and they work for you, Kopia is not an urgent migration — all three give you encrypted, deduplicated, compressed backups, and none of them will let you down on the fundamentals. Where Kopia pulls ahead is the policy engine and the web UI: configuration lives in the repository as data, is inspectable, and is shared cleanly across many machines and many humans. Where it lags is ecosystem maturity — Borg’s append-only mode over SSH and Restic’s REST-server append-only mode are both a little more battle-tested as ransomware defences than Kopia’s equivalent (kopia repository create supports a similar concept via a separate maintenance-owner repository, but it’s newer and less documented). For a single Linux box backed up over SSH, Borg is still my answer. For “several machines, one shared repository, and a relative who needs to self-serve a restore,” Kopia is now what I reach for.

A troubleshooting session, config file version

Advertisement

Repeating flags on every machine gets old fast, so Kopia also accepts a config file. Here’s a realistic one for a nightly job:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# /etc/kopia/backup.yaml
source: /home
policy:
  retention:
    keepDaily: 7
    keepWeekly: 4
    keepMonthly: 6
  exclude:
    - "**/.cache"
    - "**/*.tmp"
  compression: zstd

Wire it into a systemd timer the same way as any other backup tool:

1
2
3
4
5
6
7
8
# /etc/systemd/system/kopia-backup.service
[Unit]
Description=Nightly Kopia snapshot

[Service]
Type=oneshot
Environment=KOPIA_PASSWORD_FILE=/etc/kopia/password
ExecStart=/usr/bin/kopia snapshot create /home /etc /var/www
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# /etc/systemd/system/kopia-backup.timer
[Unit]
Description=Run Kopia backup nightly

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=600

[Install]
WantedBy=timers.target

Mounting, browsing and checking a repository

Every backup tool eventually needs to answer “can I just grab that one file” without a full restore. Kopia mounts any snapshot as a read-only filesystem:

1
2
3
4
kopia mount k1a2b3c4@/home /mnt/kopia-restore
ls /mnt/kopia-restore
cp /mnt/kopia-restore/Documents/report.docx ~/Documents/
fusermount -u /mnt/kopia-restore

This uses FUSE under the hood, so it needs fuse3 installed and, on some distributions, membership of the fuse group. It is the fastest way to recover a handful of files without dragging an entire snapshot back down from an object store.

Integrity is checked with kopia repository verify, which walks the content index and confirms every referenced chunk is actually present and readable — the equivalent of borg check or restic check. Run it on a schedule, not just when something already looks wrong:

1
kopia repository verify --verify-files-percent=10

--verify-files-percent controls how much of the data gets read back and hash-checked versus just confirming the index is consistent; a small percentage nightly and a full verify monthly is a reasonable balance between thoroughness and the egress bill on a cloud backend.

Migrating from Restic or Borg

Kopia does not read another tool’s repository format directly — there is no kopia import-restic-repo command — so a migration means running Kopia against the source filesystem as a fresh set of initial snapshots, in parallel with the old tool, until you trust the new repository enough to retire the old one. In practice that means: stand up the Kopia repository, point it at the same source paths with a policy mirroring your existing retention, let it run for a full retention cycle (a month, typically, to be sure --keep-monthly snapshots exist), test a restore, and only then decommission the old jobs. Running two backup tools for a month costs a little disk and a little cron noise; running zero backup tools during a migration mistake costs a lot more. This overlap-then-cutover approach is the same discipline worth applying to any rehearsed bare-metal restore — never trust a new backup chain until you’ve pulled a real file back out of it.

Troubleshooting

kopia snapshot create reports “unable to connect to repository.” The repository connection is stored per-user in ~/.config/kopia/repository.config. If a systemd unit runs as a different user (or root), it will not see a connection made interactively as your login user — either run kopia repository connect under the same account the service uses, or point KOPIA_CONFIG_PATH explicitly at a shared config.

Repository size keeps growing despite retention policies. Policies control which snapshots are kept; the actual space reclamation happens during maintenance. Run kopia maintenance run --full periodically, or confirm automatic maintenance is enabled with kopia maintenance info — by default one client is elected the maintenance owner and runs GC on a schedule, but if that client is offline, cleanup silently stalls.

The web UI won’t load over HTTPS with a self-signed cert. --tls-generate-cert creates a cert that browsers will flag as untrusted. Either accept the warning on a LAN-only server, or put it behind a reverse proxy with automatic HTTPS via Caddy and a real certificate for anything reachable outside your home network.

Restores are slow from an object-store backend. Kopia fetches chunks on demand and reassembles them; large restores across many small chunks incur one round-trip per chunk unless you tune --parallel. Bump kopia restore --parallel=8 (or higher, bandwidth permitting) for a meaningfully faster large restore.

Two machines’ snapshots of “the same” directory show no deduplication benefit. Deduplication is chunk-level, so entirely different file layouts (different filesystem, different chunking boundary from a prior compression pass) can produce different chunk boundaries for effectively identical data. This is inherent to content-defined chunking and not a bug — it usually still wins over full copies, just not to the extent you might hope for on adversarial inputs.

Verdict: is it worth switching?

If you are starting from nothing, Kopia is a very reasonable default: strong deduplication, broad backend support, sane defaults, and a web UI that means you are not the only person in the house who can drive a restore. If you already have a working Borg or Restic setup and it satisfies your 3-2-1 strategy, there is no burning need to migrate — dig into ransomware-resistance specifics before trusting Kopia as your sole off-site defence, since that corner of the ecosystem is younger. Where Kopia genuinely earns its keep is multi-machine homelabs where you want one policy, one repository, and one place to click “restore” without reaching for a terminal.

Advertisement
Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.