Setting up Borg Backup on FreeBSD Server

2025-02-04

BorgBackup (short: Borg) has:

Space efficient storage of backups.

Secure, authenticated encryption.

Compression: lz4, zstd, zlib, lzma or none.

Mountable backups with FUSE.

Easy installation on multiple platforms: Linux, macOS, BSD, ...

Free software (BSD license).

Backed by a large and active open source community. [1]

In this tutorial, I use FreeBSD for server, and Gentoo Linux for client.

Server side setup

Install archivers/py-borgbackup

Create borg user for better compatibility and security:

doas adduser

Username: borg
Full name: Borg Backup Server Account
Uid (Leave empty for default):
Login group [borg]:
Login group is borg. Invite borg into other groups? []:
Login class [default]:
Shell (sh csh tcsh zsh rzsh git-shell bash rbash nologin) [sh]: sh
Home directory [/home/borg]:
Home directory permissions (Leave empty for default):
Enable ZFS encryption? (yes/no) [no]:
Use password-based authentication? [yes]:
Use an empty password? (yes/no) [no]:
Use a random password? (yes/no) [no]:
Enter password:
Enter password again:
Lock out the account after creation? [no]: no
Username    : borg
Password    : *****
Full Name   : Borg Backup Server Account
Uid         : *****
ZFS dataset : zroot/home/borg
Class       :
Groups      : borg
Home        : /home/borg
Home Mode   :
Shell       : /bin/csh
Locked      : no
OK? (yes/no) [yes]:

Use the password to log in via ssh, and copy the pub-key for normal user and root:

ssh-copy-id borg@<ip>

On server, change the permissions:

doas chown -R borg:borg /path/to/borg

Client side setup

Follow the quick start guide [2], and use borg@ip as the URL:

borg list borg@ip:/path/to/repo

It would be better to automate this:

#!/bin/sh

# Setting this, so the repo does not need to be given on the commandline:
export BORG_REPO= borg@host:/path/to/repo

# See the section "Passphrase notes" for more infos.
export BORG_PASSPHRASE='passphrase'

# some helpers and error handling:
info() { printf "\n%s %s\n\n" "$(date)" "$*" >&2; }
trap 'echo $( date ) Backup interrupted >&2; exit 2' INT TERM

info "Starting backup"

# Backup the most important directories into an archive named after
# the machine this script is currently running on:

borg create \
    ::'{hostname}-{now}' \
    --show-rc \
    --stats \
    --progress \
    ~/Documents/ \
    --exclude 'Documents/games' \
    --exclude '*/package/*' \
    --exclude '*/target/*' \
    /etc \
    /root \
    /var \
    --exclude 'var/tmp/*' \
    -C zstd \
    --list

backup_exit=$?


info "Pruning repository"

# Use the `prune` subcommand to maintain 7 daily, 4 weekly and 6 monthly
# archives of THIS machine. The '{hostname}-*' matching is very important to
# limit prune's operation to this machine's archives and not apply to
# other machines' archives also:

borg prune \
    --list \
    --glob-archives '{hostname}-*' \
    --show-rc \
    --keep-daily 7 \
    --keep-weekly 4 \
    --keep-monthly 6

prune_exit=$?

# actually free repo disk space by compacting segments

info "Compacting repository"

borg compact

compact_exit=$?

# use highest exit code as global exit code
global_exit=$((backup_exit > prune_exit ? backup_exit : prune_exit))
global_exit=$((compact_exit > global_exit ? compact_exit : global_exit))

if [ ${global_exit} -eq 0 ]; then
    info "Backup, Prune, and Compact finished successfully"
elif [ ${global_exit} -eq 1 ]; then
    info "Backup, Prune, and/or Compact finished with warnings"
else
    info "Backup, Prune, and/or Compact finished with errors"
fi

exit ${global_exit}

use --dry-run, --list and exit to verify the script

And since the script contains sensitive data, we restrict the unix permission to 700:

chmod 700 backup.sh
chown root:root backup.sh

Systemd timer

Would be helpful to setup a Systemd timer to automate the backup:

systemctl edit --force --full custom-borg-backup.service

[Unit]
Description=Backup the system with borg
RefuseManualStart=no
RefuseManualStop=no

[Service]
Type=oneshot
ExecStart=/root/scripts/backup.sh

systemctl edit --force --full custom-borg-backup.timer

[Unit]
Description=Backup OS with Borg
RefuseManualStart=no
RefuseManualStop=no

[Timer]
Persistent=false
OnCalendar=daily
Unit=custom-borg-backup.service

[Install]
WantedBy=default.target

Then enable with systemctl enable --now custom-borg-backup.timer

Verify with systemctl list-timers and systemctl start custom-borg-backup.service


  1. https://www.borgbackup.org/

  2. https://borgbackup.readthedocs.io/en/stable/quickstart.html