FreeBSD.software
Home/Guides/How to Set Up Automated Backups on FreeBSD with BorgBackup
tutorial·2026-04-09·8 min read

How to Set Up Automated Backups on FreeBSD with BorgBackup

Complete guide to automated backups on FreeBSD with BorgBackup: installation, repository setup, backup scripts, pruning policies, remote backups over SSH, cron scheduling, and restore procedures.

How to Set Up Automated Backups on FreeBSD with BorgBackup

BorgBackup (Borg) is a deduplicating backup tool that compresses and encrypts your data. It is fast, space-efficient, and works natively on FreeBSD. Borg's deduplication means that unchanged data across backups is stored only once, making it practical to run frequent backups without consuming massive amounts of disk space.

This guide covers the full setup: installing Borg, initializing repositories, writing backup scripts, configuring pruning policies, setting up remote backups over SSH, scheduling with cron, and performing restores.

Why BorgBackup

Borg is well-suited for FreeBSD servers because:

  • Deduplication: Only new or changed data chunks are stored. A daily backup of a 500GB dataset where 1% changes daily uses roughly 5GB of new storage per day.
  • Compression: LZ4, zstd, zlib, and lzma compression built in.
  • Encryption: AES-256-CTR with HMAC-SHA256 authentication.
  • Append-only mode: Remote repositories can be configured so the client can only add new backups, never delete existing ones -- protecting against ransomware.
  • Mountable archives: Browse any backup as a FUSE filesystem.
  • Reliable: Borg uses content-defined chunking and verifies data integrity with checksums.

Installation

Via pkg

sh
pkg install py311-borgbackup

Via Ports

sh
cd /usr/ports/archivers/py-borgbackup make install clean

Verify the installation:

sh
borg --version

You should see borg 1.4.x or similar.

FUSE Support (Optional)

To mount backup archives as filesystems, install FUSE:

sh
pkg install fusefs-libs kldload fusefs sysrc kld_list+="fusefs"

Initializing a Local Repository

A Borg repository stores all your backup archives. Create one on a separate disk, ZFS dataset, or partition:

sh
zfs create zroot/backups zfs create zroot/backups/borg

Initialize the repository with encryption:

sh
borg init --encryption=repokey /zroot/backups/borg/server-main

Borg will prompt for a passphrase. Store this passphrase securely -- without it, your backups are unrecoverable.

For unencrypted repositories (only if the storage is already encrypted, e.g., ZFS with GELI):

sh
borg init --encryption=none /zroot/backups/borg/server-main

Export the Repository Key

Critical step. If you lose the repository key and passphrase, your data is gone:

sh
borg key export /zroot/backups/borg/server-main /root/borg-key-server-main.txt

Store this key file offline -- on a USB drive, in a safe, or in a separate secure location.

Running Your First Backup

Create a backup of /etc, /usr/local/etc, and /home:

sh
borg create \ --stats \ --progress \ --compression zstd,3 \ /zroot/backups/borg/server-main::'{hostname}-{now:%Y-%m-%d_%H:%M}' \ /etc \ /usr/local/etc \ /home \ --exclude '/home/*/.cache' \ --exclude '*.tmp'

Breakdown:

  • --stats: Show size and dedup statistics after completion
  • --compression zstd,3: Use zstd compression at level 3 (good balance of speed and ratio)
  • ::'{hostname}-{now:...}': Archive name with hostname and timestamp
  • --exclude: Skip cache directories and temp files

The first backup takes the longest. Subsequent backups are incremental and much faster due to deduplication.

Building a Backup Script

For production use, wrap the backup in a script. Create /usr/local/sbin/borg-backup.sh:

sh
#!/bin/sh # /usr/local/sbin/borg-backup.sh # Automated BorgBackup script for FreeBSD # Configuration export BORG_REPO='/zroot/backups/borg/server-main' export BORG_PASSPHRASE='your-secure-passphrase-here' # Logging LOG="/var/log/borg-backup.log" exec > >(tee -a "$LOG") 2>&1 echo "=== Borg Backup Started: $(date) ===" # Create backup borg create \ --verbose \ --filter AME \ --stats \ --compression zstd,3 \ --exclude-caches \ --exclude '/home/*/.cache/*' \ --exclude '/var/tmp/*' \ --exclude '/tmp/*' \ --exclude '/var/crash/*' \ ::'{hostname}-{now:%Y-%m-%d_%H:%M:%S}' \ /etc \ /usr/local/etc \ /home \ /var/db/pkg \ /usr/local/www \ /var/db/mysql \ /root backup_exit=$? # Prune old backups borg prune \ --list \ --glob-archives '{hostname}-*' \ --keep-hourly=24 \ --keep-daily=7 \ --keep-weekly=4 \ --keep-monthly=12 \ --keep-yearly=2 prune_exit=$? # Compact repository (Borg 1.2+) borg compact compact_exit=$? # Determine overall exit status global_exit=$(( backup_exit > prune_exit ? backup_exit : prune_exit )) global_exit=$(( global_exit > compact_exit ? global_exit : compact_exit )) if [ ${global_exit} -eq 0 ]; then echo "=== Backup Completed Successfully: $(date) ===" elif [ ${global_exit} -eq 1 ]; then echo "=== Backup Completed with Warnings: $(date) ===" else echo "=== Backup FAILED: $(date) ===" fi exit ${global_exit}

Set permissions:

sh
chmod 700 /usr/local/sbin/borg-backup.sh

Note: Storing the passphrase in the script is acceptable only if the script is owned by root with 700 permissions. For higher security, use a key file or BORG_PASSCOMMAND to retrieve the passphrase from a secrets manager.

Pruning Policies

Borg's pruning controls how many archives to keep. The flags in the script above mean:

| Flag | Retention |

|---|---|

| --keep-hourly=24 | Keep one archive per hour for the last 24 hours |

| --keep-daily=7 | Keep one archive per day for the last 7 days |

| --keep-weekly=4 | Keep one archive per week for the last 4 weeks |

| --keep-monthly=12 | Keep one archive per month for the last 12 months |

| --keep-yearly=2 | Keep one archive per year for 2 years |

This provides granular recent backups and longer-term monthly/yearly snapshots. Adjust based on your storage capacity and recovery requirements.

To preview what would be pruned without actually deleting:

sh
borg prune --list --dry-run --keep-daily=7 --keep-weekly=4 /zroot/backups/borg/server-main

Remote Backups Over SSH

Borg's most powerful feature for disaster recovery is remote backup over SSH. You back up to a separate machine so that a disk failure, fire, or ransomware attack on the primary server does not destroy your backups.

Set Up the Remote Server

On the backup server, install Borg:

sh
pkg install py311-borgbackup

Create a dedicated backup user:

sh
pw useradd -n borgbackup -m -s /bin/sh mkdir -p /home/borgbackup/.ssh chmod 700 /home/borgbackup/.ssh

Create a repository on the backup server:

sh
su - borgbackup borg init --encryption=repokey /home/borgbackup/repos/server-main

Configure SSH Key Authentication

On the client (primary server):

sh
ssh-keygen -t ed25519 -f /root/.ssh/borg_key -N ""

Copy the public key to the backup server:

sh
ssh-copy-id -i /root/.ssh/borg_key.pub borgbackup@backup-server.example.com

Restrict SSH Access

On the backup server, edit /home/borgbackup/.ssh/authorized_keys to restrict the key to Borg only:

sh
command="borg serve --restrict-to-path /home/borgbackup/repos --append-only",restrict ssh-ed25519 AAAA... root@primary-server

The --append-only flag means the client can create new archives but cannot delete or modify existing ones. This is critical for ransomware protection.

Update the Backup Script for Remote

Modify the environment variables in your backup script:

sh
export BORG_REPO='borgbackup@backup-server.example.com:/home/borgbackup/repos/server-main' export BORG_PASSPHRASE='your-secure-passphrase-here' export BORG_RSH='ssh -i /root/.ssh/borg_key -o BatchMode=yes'

The rest of the script remains the same. Borg handles the SSH transport transparently.

Scheduling with Cron

Add the backup script to root's crontab:

sh
crontab -e

Add:

sh
# Daily backup at 2:00 AM 0 2 * * * /usr/local/sbin/borg-backup.sh

For more frequent backups (every 6 hours):

sh
0 */6 * * * /usr/local/sbin/borg-backup.sh

Monitoring

Check the log after the first automated run:

sh
tail -50 /var/log/borg-backup.log

For email notifications on failure, add to the script:

sh
if [ ${global_exit} -ge 2 ]; then echo "Borg backup failed on $(hostname). Check /var/log/borg-backup.log" | \ mail -s "BACKUP FAILED: $(hostname)" admin@example.com fi

Restoring from Backups

List Available Archives

sh
borg list /zroot/backups/borg/server-main

Output:

shell
server-main-2026-04-08_02:00:01 Mon, 2026-04-08 02:00:01 server-main-2026-04-07_02:00:01 Sun, 2026-04-07 02:00:01 server-main-2026-04-06_02:00:02 Sat, 2026-04-06 02:00:02

List Files in an Archive

sh
borg list /zroot/backups/borg/server-main::server-main-2026-04-08_02:00:01

Restore Specific Files

sh
cd / borg extract /zroot/backups/borg/server-main::server-main-2026-04-08_02:00:01 etc/pf.conf

This restores etc/pf.conf relative to the current directory.

Restore Everything

sh
cd /tmp/restore borg extract /zroot/backups/borg/server-main::server-main-2026-04-08_02:00:01

Mount an Archive (FUSE)

Browse a backup interactively:

sh
mkdir /mnt/borg borg mount /zroot/backups/borg/server-main::server-main-2026-04-08_02:00:01 /mnt/borg ls /mnt/borg/etc cp /mnt/borg/usr/local/etc/nginx/nginx.conf /tmp/ umount /mnt/borg

Backing Up Databases

Databases require consistent dumps before backing up. Add pre-backup commands to your script:

PostgreSQL

sh
su -l postgres -c "pg_dumpall > /var/db/postgres/backup/all_databases.sql"

MySQL/MariaDB

sh
mysqldump --all-databases --single-transaction > /var/db/mysql/backup/all_databases.sql

ZFS Snapshots Before Backup

For ZFS datasets, take a snapshot before the Borg backup to ensure consistency:

sh
zfs snapshot zroot/var/db/mysql@pre-borg borg create ... /var/db/mysql zfs destroy zroot/var/db/mysql@pre-borg

Repository Maintenance

Verify Repository Integrity

Run periodic integrity checks:

sh
borg check /zroot/backups/borg/server-main

Add a monthly check to cron:

sh
0 4 1 * * borg check /zroot/backups/borg/server-main >> /var/log/borg-check.log 2>&1

Repository Size

Check how much space your repository uses:

sh
borg info /zroot/backups/borg/server-main

This shows total size, deduplicated size, and compression ratio.

FAQ

How much disk space does BorgBackup need?

The first backup uses roughly the same space as the original data (minus compression). Subsequent backups only store changed chunks. A typical server backing up daily uses 1-5% additional space per backup after deduplication.

Is BorgBackup better than rsync for FreeBSD backups?

For most use cases, yes. Borg provides deduplication, compression, encryption, and versioned archives. rsync is simpler but creates full copies or requires complex wrapper scripts for versioning. Use rsync for simple file mirroring; use Borg for proper backups.

Can I back up ZFS snapshots with Borg?

Borg backs up files, not ZFS snapshots directly. For ZFS-native replication, use zfs send/receive. Borg is complementary -- use it for off-site backups where the remote does not have ZFS.

How do I change my Borg passphrase?

sh
borg key change-passphrase /zroot/backups/borg/server-main

What happens if a backup is interrupted?

Borg uses a transaction log. An interrupted backup leaves no partial archive. The next backup run starts fresh and deduplication ensures it is still efficient.

Can I run Borg backups on a live system?

Yes, but database files should be dumped first (see the database section above). Regular files can be backed up while the system is running. Borg handles open files gracefully.

Get more FreeBSD guides

Weekly tutorials, security advisories, and package updates. No spam.