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
shpkg install py311-borgbackup
Via Ports
shcd /usr/ports/archivers/py-borgbackup make install clean
Verify the installation:
shborg --version
You should see borg 1.4.x or similar.
FUSE Support (Optional)
To mount backup archives as filesystems, install FUSE:
shpkg 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:
shzfs create zroot/backups zfs create zroot/backups/borg
Initialize the repository with encryption:
shborg 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):
shborg 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:
shborg 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:
shborg 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:
shchmod 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:
shborg 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:
shpkg install py311-borgbackup
Create a dedicated backup user:
shpw useradd -n borgbackup -m -s /bin/sh mkdir -p /home/borgbackup/.ssh chmod 700 /home/borgbackup/.ssh
Create a repository on the backup server:
shsu - borgbackup borg init --encryption=repokey /home/borgbackup/repos/server-main
Configure SSH Key Authentication
On the client (primary server):
shssh-keygen -t ed25519 -f /root/.ssh/borg_key -N ""
Copy the public key to the backup server:
shssh-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:
shcommand="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:
shexport 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:
shcrontab -e
Add:
sh# Daily backup at 2:00 AM 0 2 * * * /usr/local/sbin/borg-backup.sh
For more frequent backups (every 6 hours):
sh0 */6 * * * /usr/local/sbin/borg-backup.sh
Monitoring
Check the log after the first automated run:
shtail -50 /var/log/borg-backup.log
For email notifications on failure, add to the script:
shif [ ${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
shborg list /zroot/backups/borg/server-main
Output:
shellserver-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
shborg list /zroot/backups/borg/server-main::server-main-2026-04-08_02:00:01
Restore Specific Files
shcd / 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
shcd /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:
shmkdir /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
shsu -l postgres -c "pg_dumpall > /var/db/postgres/backup/all_databases.sql"
MySQL/MariaDB
shmysqldump --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:
shzfs 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:
shborg check /zroot/backups/borg/server-main
Add a monthly check to cron:
sh0 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:
shborg 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?
shborg 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.