VPS Snaps

How to back up a Linux server with Kopia

Kopia is free, open-source backup software that splits files into chunks, stores each chunk once, encrypts everything on the server and writes it to S3-compatible storage, an SFTP server or a local disk. On Ubuntu, install the kopia package from Kopia's apt repository, create a repository with kopia repository create s3, set a retention and compression policy, and run kopia snapshot create from a systemd timer. The repository password is the only way to decrypt the backups, so store it off the server before the first one.

11 min readUpdated Checked against official documentation

What Kopia is and how it stores backups

Kopia is open source (Apache 2.0), written in Go and shipped as one kopia binary; the current release in October 2026 is 0.23.1, from 16 June 2026. It saves directories as snapshots into a repository (a bucket prefix, an SFTP folder or a local directory), and policies stored in the repository decide what to keep, skip and compress. It uploads only the content-defined chunks the repository does not hold yet, so every snapshot after the first is incremental, and it always encrypts on the server first (AES256-GCM-HMAC-SHA256 by default).

KopiaUI (a desktop app) and kopia server (a web UI) exist, but a server needs neither. Other tools for the same job: restic, BorgBackup and Duplicati.

Install Kopia from its apt repository

Terminal
sudo install -d -m 0755 /etc/apt/keyrings
Terminal
curl -s https://kopia.io/signing-key | sudo gpg --dearmor -o /etc/apt/keyrings/kopia-keyring.gpg
Terminal
echo "deb [signed-by=/etc/apt/keyrings/kopia-keyring.gpg] http://packages.kopia.io/apt/ stable main" | sudo tee /etc/apt/sources.list.d/kopia.list
Terminal
sudo apt update
Terminal
sudo apt install kopia
Terminal
kopia --version
  • /etc/apt/keyrings exists on recent Ubuntu releases; the first command only makes sure. signed-by trusts the key for this repository only.
  • gpg --show-keys /etc/apt/keyrings/kopia-keyring.gpg should show 7FB99DFD47809F0D5339D7D92273699AFD56A556 and Kopia Builder <[email protected]>, the key Kopia's docs show signing its releases. The URL is plain http://, as Kopia gives it; apt refuses any package that does not match the signed index.
  • stable carries releases (testing and unstable also exist). Skip kopia-ui, the desktop app. The binary is /usr/bin/kopia and updates with apt upgrade.

Pin Kopia's files under /etc/kopia

Kopia finds its files through $HOME (~/.config/kopia, ~/.cache/kopia), and a shell, a cron job and a systemd service can disagree about $HOME. Name every path once, in a root-only file they all read:

Terminal
sudo install -d -m 700 /etc/kopia
/etc/kopia/env
KOPIA_CONFIG_PATH=/etc/kopia/repository.config
KOPIA_CACHE_DIRECTORY=/var/cache/kopia
KOPIA_LOG_DIR=/var/log/kopia
KOPIA_CHECK_FOR_UPDATES=false
AWS_ACCESS_KEY_ID=your-access-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
Terminal
sudo chmod 600 /etc/kopia/env
  • KOPIA_CONFIG_PATH is the connection file. On create or connect, Kopia saves the bucket, endpoint and keys in it, and the password beside it in repository.config.kopia-password (base64, not encrypted), so keep /etc/kopia at mode 700.
  • KOPIA_CACHE_DIRECTORY and KOPIA_LOG_DIR place the local cache and the log files. KOPIA_CHECK_FOR_UPDATES=false stops update checks; apt handles upgrades.
  • AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY hold the bucket key on any S3-compatible provider. The --access-key and --secret-access-key flags override them, but typed keys land in shell history.

Load the file in a root shell; set -a exports every variable it defines:

Terminal
sudo -i
Terminal
set -a; . /etc/kopia/env; set +a

Create the repository in an S3-compatible bucket

Kopia does not create buckets. Make one and a key for it first (an S3 bucket for backups, Backblaze B2), then generate a password and put it in your password manager:

Terminal
head -c 32 /dev/urandom | base64
Terminal
kopia repository create s3 --bucket=my-backups --prefix=web1/ --endpoint=s3.us-west-004.backblazeb2.com
  • --bucket names the existing bucket. --prefix=web1/ keeps this server's repository in a folder; the trailing slash makes it one.
  • --endpoint is the S3 host name only, with no https:// and no path. It defaults to s3.amazonaws.com, so on AWS leave it out and add --region with the bucket's region, as any provider that needs a region requires. B2 endpoints follow s3.<region>.backblazeb2.com.

Kopia asks for the password twice, prints the formats it chose (BLAKE2B-256-128 hashing, AES256-GCM-HMAC-SHA256 encryption, scrypt key derivation, DYNAMIC-4M-BUZHASH splitting) and connects. It then suggests a provider test, which writes and reads test objects:

Terminal
kopia repository validate-provider

In Kopia's words: "There is absolutely no way to restore any of your backed up files from a repository if you forget your password!" kopia repository change-password works only while a machine is still connected. Keep the password off this server.

On Backblaze B2, an S3 delete only hides a file and hidden versions are kept by default, so the bucket keeps growing after Kopia deletes data unless a lifecycle rule removes them.

Or use an SFTP server or a local disk

The backup server needs only SFTP, not Kopia. Create a user there (kopia) with a key made for this job (SSH key authentication), connect once to record the host key (then type bye), and create the repository:

Terminal
sftp -i /root/.ssh/kopia_ed25519 [email protected]
Terminal
kopia repository create sftp --host=backup.example.com --username=kopia --keyfile=/root/.ssh/kopia_ed25519 --known-hosts=/root/.ssh/known_hosts --path=/srv/kopia/web1

--keyfile is the private key, --known-hosts the file holding the server's host key (required) and --path the folder there. For a mounted disk, use kopia repository create filesystem --path=/mnt/backup/kopia; Kopia's docs warn that network filesystems without full POSIX behavior can corrupt a repository.

Set retention, compression and ignore rules

Terminal
kopia policy set --global --compression=zstd --keep-latest=7 --keep-daily=14 --keep-weekly=8 --keep-monthly=12 --keep-annual=2
  • --global sets the policy every snapshot inherits; kopia policy set /var/www ... overrides it for one path.
  • --compression=zstd compresses file data, stored uncompressed by default. Kopia's FAQ calls zstd one of the top choices. It applies to new uploads only.
  • --keep-latest=7 keeps the 7 newest snapshots, whatever their age.
  • The period rules keep the newest snapshot per day, week, month or year, but only within that many periods before the newest snapshot: --keep-daily=14 looks back 14 days, not 14 days that have backups. Unset rules keep the defaults: latest 10, hourly 48, daily 7, weekly 4, monthly 24, annual 3.

Kopia applies the policy right after each snapshot of a source. kopia policy show --global prints it; kopia snapshot expire --all reports what it would remove and deletes only with --delete.

/var/www/.kopiaignore
# rules apply to this directory and everything below it
*.log
node_modules/
/*/wp-content/cache/

Rules work like .gitignore: a pattern with no slash (apart from a trailing one) matches at any depth, a leading or inner / anchors it here, a trailing / means directories only, and ! re-includes. Policies take the same rules with --add-ignore. Directories holding a CACHEDIR.TAG file are skipped by default, and --one-file-system=true in a policy stops snapshots crossing into other mounts.

Take and list snapshots

Terminal
kopia snapshot create /etc /var/www /root

Each path becomes its own source (root@web1:/var/www) with its own history and retention, and later runs upload only new chunks. Unreadable files are reported as Error when processing lines; Kopia saves the snapshot without them, then exits with code 1.

Terminal
kopia snapshot list

It shows each snapshot's time, root ID (starting with k), size and the rules keeping it, such as daily-3. --all includes every user and host. Database files change while Kopia reads them, so dump them first (pg_dump, mysqldump) and snapshot the dumps:

/usr/local/sbin/dump-databases.sh
#!/bin/sh
set -eu
install -d -m 700 /var/backups/db
runuser -u postgres -- pg_dump -Fc mydb > /var/backups/db/mydb.dump.tmp
mv /var/backups/db/mydb.dump.tmp /var/backups/db/mydb.dump
Terminal
chmod 700 /usr/local/sbin/dump-databases.sh

set -e stops at a failed dump, so it never replaces the last good one; the timer below runs the script before each snapshot. Kopia's built-in alternative is a --before-snapshot-root-action policy, which needs --enable-actions at connect.

Restore files

Terminal
kopia snapshot restore /var/www/site /srv/restore/site

Given a path, Kopia restores it from the newest snapshot containing it, creating the target and overwriting files already there, so use an empty folder. --snapshot-time takes yesterday, 3d-ago or a date such as 2026-10-01 (the newest snapshot on or before that day):

Terminal
kopia snapshot restore --snapshot-time=2026-10-01 /var/www/site/wp-config.php /srv/restore/wp-config.php

A target ending in .zip, .tar or .tar.gz becomes an archive. To browse, mount the repository; the command holds the terminal until Ctrl-C unmounts it, and all shows every source:

Terminal
mkdir -p /mnt/kopia
Terminal
kopia mount all /mnt/kopia

On a replacement server, install Kopia, recreate and load /etc/kopia/env, and connect with the same bucket flags (Kopia asks for the password). A bare path only matches the current user@host, so on a new hostname name the old source. Full steps: restoring a server from backup.

Terminal
kopia repository connect s3 --bucket=my-backups --prefix=web1/ --endpoint=s3.us-west-004.backblazeb2.com
Terminal
kopia snapshot restore root@web1:/var/www /var/www

Verify the repository

Terminal
kopia snapshot verify --verify-files-percent=5

Without the flag, verify walks every snapshot and checks that each file's chunks are indexed and stored, without downloading file data. --verify-files-percent=5 also downloads, decrypts and checks a random 5% of files, which costs egress where providers charge for it; Kopia's docs suggest 1% daily or 100% now and then. A verify proves the data is intact; a test restore proves you can use it.

Maintenance: who runs it, and when

Expired snapshots free no space until maintenance deletes data nothing uses: quick maintenance hourly by default, full maintenance (which removes unreferenced data and compacts packs) every 24 hours. Kopia runs whichever is due at the end of a successful command such as snapshot create, but only for the maintenance owner, the user@host that created the repository (here root@web1). Other machines never run it, and a backup that fails every night never triggers it.

Terminal
kopia maintenance info

It shows the owner and recent runs. kopia maintenance run --full runs it now; kopia maintenance set --owner=me makes this machine the owner, for example after a rebuild under a new hostname. Full maintenance waits out safety delays, so space can take hours to come back, and it refuses to run when the clock is more than 5 minutes off.

Kopia's cache may grow to 5,000 MB of data plus 5,000 MB of metadata. On a small VPS, kopia cache set --content-cache-size-mb=1000 --metadata-cache-size-mb=1000 lowers both soft limits.

Run it nightly with a systemd timer

/etc/systemd/system/kopia-backup.service
[Unit]
Description=Kopia backup
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
EnvironmentFile=/etc/kopia/env
ExecStart=/usr/local/sbin/dump-databases.sh
ExecStart=/usr/bin/kopia snapshot create /etc /var/www /root /var/backups/db
ExecStart=/usr/bin/kopia snapshot verify --verify-files-percent=1
/etc/systemd/system/kopia-backup.timer
[Unit]
Description=Nightly Kopia backup

[Timer]
OnCalendar=*-*-* 02:30:00
RandomizedDelaySec=15m
Persistent=true

[Install]
WantedBy=timers.target
  • Type=oneshot runs the ExecStart lines in order and stops at the first failure, so a failed dump skips the snapshot and the unit shows as failed. Drop the dump line on servers without a database.
  • EnvironmentFile reads the same /etc/kopia/env. Persistent=true runs a missed backup at boot; RandomizedDelaySec=15m spreads servers that share a bucket.
Terminal
sudo systemctl daemon-reload
Terminal
sudo systemctl enable --now kopia-backup.timer
Terminal
sudo systemctl start kopia-backup.service

The last command runs it once now; journalctl -u kopia-backup.service shows the result. With cron instead:

/etc/cron.d/kopia-backup
30 2 * * * root set -a; . /etc/kopia/env; set +a; /usr/bin/kopia snapshot create /etc /var/www /root >> /var/log/kopia-cron.log 2>&1

Schedules in a policy (--snapshot-interval, --snapshot-time) are carried out only by a running kopia server or KopiaUI, not by the plain command line.

Stop the server from deleting its own backups

Root on web1 holds the password and a key that can delete. Kopia can lock everything it writes with S3 Object Lock, in a bucket created with Object Lock enabled:

Terminal
kopia repository create s3 --bucket=my-locked-backups --prefix=web1/ --endpoint=s3.us-west-004.backblazeb2.com --retention-mode COMPLIANCE --retention-period 30d
Terminal
kopia maintenance set --extend-object-locks true

In COMPLIANCE mode nobody, not even the account's root user, can delete an object before its 30-day --retention-period ends; Kopia's docs strongly recommend it over GOVERNANCE. --extend-object-locks true makes full maintenance renew the locks, which needs a retention period at least a day longer than the full maintenance interval. Locked data cannot be removed early even by you, so set a bucket quota and a lifecycle rule; see protecting backups from ransomware.

Common errors

ErrorFix
repository is not connected. See https://kopia.io/docs/repositories/KOPIA_CONFIG_PATH is not set in this shell or job. Load /etc/kopia/env.
required flag(s) '--access-key', '--secret-access-key' not providedThe bucket keys are not in the environment. Load /etc/kopia/env with set -a.
Endpoint url cannot have fully qualified paths.--endpoint contains https:// or a path. Give the host name only.
found existing data in storage locationThat bucket and prefix already hold data. Use kopia repository connect s3, or a new --prefix.
invalid repository passwordWrong password for this repository.
Found 1 fatal error(s) while snapshottingFiles listed above it could not be read. Fix permissions, or accept gaps with kopia policy set <path> --ignore-file-errors=true.
maintenance must be run by designated user: root@web1Run it on the owner, or take ownership with kopia maintenance set --owner=me.
clock skew detected: local clock is out of sync with repository timestamp by more than allowed 5m0sFix the clock; timedatectl shows whether time sync is on.
must provide either --known-hosts or --known-hosts-dataSFTP needs --known-hosts=/root/.ssh/known_hosts.

Frequently asked questions

Is Kopia free?
Yes. Kopia is open source under the Apache 2.0 licence. You pay only for the storage you use.
Does Kopia compress backups?
Not file data by default. Turn it on in a policy, such as kopia policy set --global --compression=zstd; it applies to later uploads.
What happens if I forget my Kopia repository password?
Nothing can be restored. A machine still connected can set a new one with kopia repository change-password; otherwise there is no way in.
Why does my Kopia bucket not shrink after snapshots expire?
Space returns only when full maintenance runs on the maintenance owner, after safety delays. kopia maintenance info shows the owner and recent runs.
Do I need KopiaUI or kopia server on a Linux server?
No. The kopia command does everything; schedule it with a systemd timer or cron.

How this was checked

Commands, limits and prices were checked against these official pages, on October 4, 2026: