Files
Main/2 Personal/Home Lab/Backup System - Kopia Server Setup.md
Obsidian-MBPM4 9aae367408 vault backup: 2026-04-20 12:33:43
Affected files:
.obsidian/workspace.json
2 Personal/Home Lab/Backup System - Kopia Server Setup.md
2026-04-20 12:33:43 +02:00

18 KiB
Raw Permalink Blame History

title, created_date, updated_date, aliases, tags
title created_date updated_date aliases tags
Backup System - Kopia Server Setup 2026-04-20 2026-04-20

Backup System - Kopia Server Setup

Overview

This document describes the setup we built for a self-hosted Kopia backup server in the homelab.

Final architecture

  • Hypervisor: Proxmox
  • Backup server runtime: Debian VM on Proxmox (IP:192.168.1.54 - IP managed by pi-hole)
  • Backup repository storage: Synology NAS via NFS
  • Backup service: Kopia Repository Server
  • Remote/private access: intended via Tailscale or LAN
  • First client: MacBook
  • TLS: self-generated Kopia TLS certificate

Why this architecture was chosen

We first tried to run Kopia in an LXC container. That led to multiple issues:

  • NFS mount permission issues with an unprivileged LXC
  • AppArmor / systemd problems with newer Debian / systemd in LXC
  • black Proxmox console / awkward LXC behavior

Because of that, we switched to a VM, which is the cleaner and more robust setup for this use case.


Final design

Components

  • Debian VM runs Kopia Repository Server
  • Synology NAS stores the actual backup repository blobs
  • Kopia clients connect to the Repository Server over HTTPS
  • MacBook connects as claudio@macbook-main

Repository model

Important distinction:

  • The repository itself is stored on the NAS filesystem
  • The Repository Server is the HTTP/HTTPS layer in front of it
  • Users connect to the server, not directly to the NAS share

This is better than mounting the share directly on each laptop because:

  • clients do not depend on a mounted NAS path
  • you get per-user accounts on the server
  • easier multi-user setup
  • easier remote use over VPN/Tailscale

Synology setup

Shared folder

A dedicated shared folder was used on Synology:

  • kopia-repository

NFS export

The NFS export path used was:

192.168.1.34:/volume1/kopia-repository

Synology NFS settings

Recommended / used settings:

  • Privilege: Read/Write
  • Squash: No mapping
  • Security: sys
  • Allow connections from non-privileged ports: enabled if needed

Notes:

  • Squash: No mapping is fine for a dedicated Kopia repository share.
  • In the LXC attempt, permissions still failed because of UID/GID mapping issues with unprivileged containers.

Failed LXC attempt and why we abandoned it

We first tried to run Kopia in a Proxmox LXC.

What we tried

  • Created an LXC
  • Tried mounting NFS inside the LXC
  • Then switched to mounting NFS on the Proxmox host and bind-mounting it into the LXC
  • Added /dev/net/tun for Tailscale / ZeroTier
  • Enabled nesting=1 because of Debian/systemd issues

Problems encountered

1. Wrong mount point created via Proxmox UI

We accidentally created an extra LXC disk instead of a bind mount.

We had this wrong line:

mp0: local-lvm:vm-102-disk-1,mp=/mnt/pve/kopia-repo,size=8G

This was not a bind mount from the host.

The correct bind mount syntax was:

mp0: /mnt/pve/kopia-repo,mp=/srv/kopia-repo

2. Console issues / black screen

The LXC booted, but the Proxmox console was black.

pct enter worked, but pct console did not work reliably.

3. systemd / AppArmor issues

We saw warnings such as:

  • Systemd 257 detected. You may need to enable nesting.
  • AppArmor denials related to userns_create, mount, journald, networkd, etc.

This was partially fixed with:

pct set 102 --features nesting=1
pct restart 102

4. NFS permission issues in unprivileged LXC

Even when the share was mounted correctly, writes failed inside the container:

touch /srv/kopia-repo/testfile
# Permission denied

This happened because unprivileged container root is UID-mapped and Synology/NFS did not allow writes the way we wanted.

Conclusion

The LXC route was abandoned because it caused unnecessary complexity for a simple service.

We switched to a Debian VM, which is simpler and more maintainable.


VM setup

Assumptions

  • Debian VM on Proxmox
  • VM has network access to the Synology NAS
  • NFS share exists on Synology
  • Kopia repository will live on the NAS

Base packages installed

On the VM, we installed the following:

apt update
apt install -y nfs-common curl ca-certificates gnupg nano

NFS mount inside the VM

Mountpoint creation

mkdir -p /srv/kopia-repo
mkdir -p /var/lib/kopia
chmod 700 /var/lib/kopia

Manual test mount

mount -t nfs 192.168.1.34:/volume1/kopia-repository /srv/kopia-repo

Validation commands

df -h /srv/kopia-repo
touch /srv/kopia-repo/testfile
ls -l /srv/kopia-repo/testfile
rm /srv/kopia-repo/testfile

At this stage, the VM approach worked properly.

Persistent mount in /etc/fstab

We added:

192.168.1.34:/volume1/kopia-repository /srv/kopia-repo nfs defaults,_netdev 0 0

Then tested with:

umount /srv/kopia-repo
mount -a
df -h /srv/kopia-repo

Kopia installation on the VM

APT repo setup

We used the official Kopia apt repository.

Commands used:

install -d -m 0755 /etc/apt/keyrings
curl -s https://kopia.io/signing-key | gpg --dearmor -o /etc/apt/keyrings/kopia-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/kopia-keyring.gpg] http://packages.kopia.io/apt/ stable main" > /etc/apt/sources.list.d/kopia.list
apt update
apt install -y kopia

Verify installation

kopia --version

Kopia repository creation

Repository directory

mkdir -p /srv/kopia-repo/repository

Create repository

We created a filesystem repository on the NFS-backed path:

kopia repository create filesystem --path=/srv/kopia-repo/repository

During this step, a repository password was chosen.

This password is critical. It encrypts the repository contents.


Environment variables

We used an environment file with exported variables for passwords.

Important shell note:

  • "$VAR" expands the variable
  • '$VAR' does not expand the variable

So this is wrong:

--server-password='$KOPIA_SRV_PW'

And this is correct:

--server-password="$KOPIA_SRV_PW"

Variables used

Example variables:

export KOPIA_REPO_PW="..."
export KOPIA_SRV_CTRL_PW="..."
export KOPIA_SRV_PW="..."

And for manual startup we exported:

export KOPIA_PASSWORD="$KOPIA_REPO_PW"

First manual Kopia server start attempts

Initial attempt without TLS

We first tried something like:

kopia server start \
  --address=0.0.0.0:51515 \
  --server-control-username=server-control \
  --server-control-password="$KOPIA_SRV_CTRL_PW" \
  --server-username=kopia \
  --server-password="$KOPIA_SRV_PW"

This failed with the message:

  • TLS not configured. To start server without encryption pass --insecure

So the server did not actually listen on the port.

Working manual TLS start

The working startup command was:

kopia server start \
    --tls-generate-cert \
    --tls-cert-file ~/my.cert \
    --tls-key-file ~/my.key \
    --address 0.0.0.0:51515 \
    --server-control-username control

Notes:

  • This generated a self-signed TLS cert and key.
  • After generation, future starts must not use --tls-generate-cert again.

Adding the first Kopia user

We created the first Mac user with:

kopia server user add claudio@macbook-main

This prompts for a password for that user.

This user/password is what the Mac client uses to connect.


Server refresh issue and root cause

We saw this error:

kopia server refresh ...
400 Bad Request: not connected

And from the Mac / KopiaUI we saw:

  • not connected to a direct repository

Root cause

The Repository Server was starting, but the process running under systemd was not connected to the repository.

This happened because:

  • the repository had been created and connected as user cef
  • but the systemd service was running as root by default
  • root did not have the Kopia repository config/session

Fix

We changed the systemd service to run as user cef.

That solved the issue.


TLS certificate handling

Move cert and key to stable location

We moved the generated files out of the home directory:

sudo mkdir -p /etc/kopia
sudo mv ~/my.cert /etc/kopia/server.cert
sudo mv ~/my.key /etc/kopia/server.key
sudo chmod 600 /etc/kopia/server.key
sudo chmod 644 /etc/kopia/server.cert

Important permission fix

Because the service runs as user cef, that user needed access to the cert and key:

sudo chown cef:cef /etc/kopia/server.cert /etc/kopia/server.key
sudo chmod 600 /etc/kopia/server.key
sudo chmod 644 /etc/kopia/server.cert

Environment file for systemd

We created:

sudo nano /etc/kopia-server.env

Contents:

KOPIA_PASSWORD=YOUR_REPOSITORY_PASSWORD
KOPIA_SRV_CTRL_PW=YOUR_SERVER_CONTROL_PASSWORD
KOPIA_SRV_PW=YOUR_WEB_UI_PASSWORD

Permissions:

sudo chown root:cef /etc/kopia-server.env
sudo chmod 640 /etc/kopia-server.env

Final systemd service

We created:

sudo nano /etc/systemd/system/kopia-server.service

Final service:

[Unit]
Description=Kopia Repository Server
After=network-online.target remote-fs.target
Wants=network-online.target
Requires=remote-fs.target

[Service]
Type=simple
User=cef
Group=cef
EnvironmentFile=/etc/kopia-server.env
ExecStart=/usr/bin/kopia server start \
  --tls-cert-file=/etc/kopia/server.cert \
  --tls-key-file=/etc/kopia/server.key \
  --address=0.0.0.0:51515 \
  --server-control-username=control \
  --server-control-password=${KOPIA_SRV_CTRL_PW} \
  --server-username=kopia \
  --server-password=${KOPIA_SRV_PW}
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Enable and start

sudo systemctl daemon-reload
sudo systemctl enable --now kopia-server
sudo systemctl status kopia-server --no-pager

Check if listening

ss -ltnp | grep 51515

Certificate fingerprint

Because the TLS certificate is self-generated, clients must trust it using the SHA256 fingerprint.

Get fingerprint

On the VM:

openssl x509 -in /etc/kopia/server.cert -noout -fingerprint -sha256 | sed 's/://g' | cut -f 2 -d =

Save the resulting fingerprint.


Refreshing server credentials

Once the service was working, refresh needed to use:

  • HTTPS
  • control username/password
  • server certificate fingerprint

Command:

kopia server refresh \
  --address=https://127.0.0.1:51515 \
  --server-control-username=control \
  --server-control-password="$KOPIA_SRV_CTRL_PW" \
  --server-cert-fingerprint=YOUR_FINGERPRINT

If you get not connected, the server process is not connected to the repository context. Check that the service runs as the same user that has a valid Kopia repository config.


MacBook setup

Install Kopia on macOS

We used Homebrew:

brew install kopia
brew install kopiaui

Connect the Mac to the Repository Server

We used the CLI first, because it is more reliable for initial connection than KopiaUI.

Command:

kopia repository connect server \
  --url=https://YOUR_VM_IP:51515 \
  --server-cert-fingerprint=YOUR_CERT_FINGERPRINT \
  --override-username=claudio \
  --override-hostname=macbook-main

The login password here is the password for the Kopia server user:

  • claudio@macbook-main

Verify connection

kopia repository status

MacBook first test backup

Create a test folder

mkdir -p ~/kopia-test
echo "hello kopia" > ~/kopia-test/file1.txt
date > ~/kopia-test/file2.txt

Create first snapshot

kopia snapshot create ~/kopia-test

List snapshots

kopia snapshot list

Test restore

mkdir -p ~/kopia-restore-test
kopia restore latest ~/kopia-restore-test
ls -la ~/kopia-restore-test

This validates the full chain:

  • VM
  • NFS mount
  • repository
  • Kopia Repository Server
  • Mac client connection
  • backup
  • restore

Automatic backup on the Mac

Basic idea

The recommended model is:

  1. connect the Mac to the repository once
  2. define backup roots
  3. use kopia snapshot create --all on a schedule

Suggested first backup roots

Start simple. For example:

  • ~/Documents
  • ~/Desktop
  • local project folders

Do not immediately back up all of ~/Library.

Initial snapshots for real backup roots

Example:

kopia snapshot create ~/Documents
kopia snapshot create ~/Desktop

Set retention / scheduling policy

Example:

kopia policy set ~/Documents \
  --snapshot-interval=12h \
  --keep-latest=14 \
  --keep-daily=14 \
  --keep-weekly=8 \
  --keep-monthly=12

kopia policy set ~/Desktop \
  --snapshot-interval=12h \
  --keep-latest=14 \
  --keep-daily=14 \
  --keep-weekly=8 \
  --keep-monthly=12

Robust automatic execution with launchd

Create:

~/Library/LaunchAgents/com.claudio.kopia-backup.plist

Contents:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>com.claudio.kopia-backup</string>

    <key>ProgramArguments</key>
    <array>
      <string>/opt/homebrew/bin/kopia</string>
      <string>snapshot</string>
      <string>create</string>
      <string>--all</string>
      <string>--no-progress</string>
    </array>

    <key>StartInterval</key>
    <integer>21600</integer>

    <key>RunAtLoad</key>
    <true/>

    <key>StandardOutPath</key>
    <string>/tmp/kopia-backup.out</string>

    <key>StandardErrorPath</key>
    <string>/tmp/kopia-backup.err</string>
  </dict>
</plist>

Load it:

launchctl unload ~/Library/LaunchAgents/com.claudio.kopia-backup.plist 2>/dev/null || true
launchctl load ~/Library/LaunchAgents/com.claudio.kopia-backup.plist
launchctl list | grep kopia

This runs every 6 hours (21600 seconds).

Verify automatic backup

kopia snapshot list

And occasionally:

kopia restore latest ~/kopia-restore-test-2

How to add another user, for example your spouse

The model is:

  • create a new user on the Kopia server
  • connect that users machine with a fixed username/hostname identity
  • create backup sources from that machine

Example: spouse on Windows

Suppose you want:

  • username: partner
  • hostname: windows-laptop

1. Create the user on the Kopia server VM

On the VM:

kopia server user add partner@windows-laptop

Set a password when prompted.

2. Refresh credentials if needed

kopia server refresh \
  --address=https://127.0.0.1:51515 \
  --server-control-username=control \
  --server-control-password="$KOPIA_SRV_CTRL_PW" \
  --server-cert-fingerprint=YOUR_FINGERPRINT

3. Install Kopia on the spouses machine

For Windows, install Kopia / KopiaUI from the official installer.

4. Connect that machine to the server

From CLI, the equivalent pattern is:

kopia repository connect server \
  --url=https://YOUR_VM_IP:51515 \
  --server-cert-fingerprint=YOUR_CERT_FINGERPRINT \
  --override-username=partner \
  --override-hostname=windows-laptop

Then enter the password for:

  • partner@windows-laptop

5. Create first backup sources on that machine

For example on Windows:

  • Documents
  • Desktop
  • Pictures

6. Run first backup and test restore

Do exactly the same test pattern as on the Mac:

  • create first snapshot
  • list snapshots
  • restore a test folder

Naming convention recommendation

Use stable names so future maintenance is simple.

Examples:

  • claudio@macbook-main
  • claudio@windows-main
  • partner@windows-laptop
  • partner@android-photos if you ever use a separate flow later

Maintenance and troubleshooting

Check service status

sudo systemctl status kopia-server --no-pager

View Kopia server logs

sudo journalctl -u kopia-server -n 100 --no-pager

Check if port is listening

ss -ltnp | grep 51515

Check repository status as service user

kopia repository status

If this fails under the service user context, the Repository Server will not work correctly.

Test local HTTPS endpoint

curl -k https://127.0.0.1:51515/

Get certificate fingerprint again

openssl x509 -in /etc/kopia/server.cert -noout -fingerprint -sha256 | sed 's/://g' | cut -f 2 -d =

Common failure: wrong shell quoting

Wrong:

--server-password='$KOPIA_SRV_PW'

Correct:

--server-password="$KOPIA_SRV_PW"

Common failure: server not connected

Symptoms:

  • 400 Bad Request: not connected
  • Mac / UI error: not connected to a direct repository

Fix:

  • make sure systemd service runs as the same user that created / connected the repository
  • in this setup that was cef

Common failure: browser works but Mac client fails

This usually means:

  • HTTPS listener is fine
  • web auth is fine
  • but the server has no repository connection or client trust settings are wrong

Check:

  • kopia repository status
  • systemd service user
  • cert fingerprint on client

Important files in this setup

On the VM

  • Repository storage:
    • /srv/kopia-repo/repository
  • NFS mountpoint:
    • /srv/kopia-repo
  • TLS cert:
    • /etc/kopia/server.cert
  • TLS key:
    • /etc/kopia/server.key
  • systemd env file:
    • /etc/kopia-server.env
  • systemd service:
    • /etc/systemd/system/kopia-server.service

On the Mac

  • launchd job:
    • ~/Library/LaunchAgents/com.claudio.kopia-backup.plist

What not to forget later

  • Do not rerun --tls-generate-cert on every start.
  • Keep the repository password safe.
  • Keep the server control password safe.
  • Keep the Web UI password safe.
  • Keep the certificate fingerprint documented.
  • Test restores periodically, not just backups.
  • Do not assume browser access means repository connectivity is correct.

Suggested next steps

  1. Finalize the Mac backup roots
  2. Test automatic backups from the Mac
  3. Add spouses machine as a second user
  4. Test restore from spouses machine too
  5. Later consider offsite replication of the Kopia repository
  6. Keep Time Machine in parallel on the Mac if you want easier full-machine restore