Configure system backup automation with BorgBackup and systemd timers

Intermediate 45 min Apr 13, 2026 207 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up automated, deduplicated backups with BorgBackup and systemd timers for reliable system protection. This tutorial covers installation, repository setup with encryption, automated scheduling, and monitoring for production environments.

Prerequisites

  • Root or sudo access
  • At least 10GB free disk space for backups
  • Basic familiarity with systemd and command line

What this solves

BorgBackup provides deduplicated, encrypted backups that save storage space and protect your data. Combined with systemd timers, you get reliable automated backups that run without cron dependencies and integrate with system logging for better monitoring.

Step-by-step installation

Update system packages

Start by updating your package manager to ensure you get the latest BorgBackup version.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install BorgBackup

Install BorgBackup from the official repositories. This provides the borg command for creating and managing backup repositories.

sudo apt install -y borgbackup
sudo dnf install -y borgbackup

Create backup directory structure

Create directories for storing backup repositories and scripts. Use proper ownership and permissions for security.

sudo mkdir -p /opt/backups/repos
sudo mkdir -p /opt/backups/scripts
sudo mkdir -p /var/log/backups

Create backup user

Create a dedicated user for running backups. This follows security best practices by avoiding root execution.

sudo useradd --system --home /opt/backups --shell /bin/bash backup-user
sudo chown -R backup-user:backup-user /opt/backups
sudo chown backup-user:backup-user /var/log/backups

Initialize BorgBackup repository

Switch to the backup user and initialize an encrypted repository. The passphrase encrypts all backup data.

sudo -u backup-user bash
cd /opt/backups
export BORG_REPO=/opt/backups/repos/system-backup
export BORG_PASSPHRASE="YourSecurePassphrase123!"
borg init --encryption=repokey-blake2 $BORG_REPO
exit
Note: Store the BORG_PASSPHRASE securely. Without it, your backups are unrecoverable. Consider using a password manager or external key management system.

Create backup script

Create the main backup script that defines what to backup, handles logging, and manages retention policies.

#!/bin/bash

BorgBackup automation script

Exit on any error

set -e

Configuration

export BORG_REPO="/opt/backups/repos/system-backup" export BORG_PASSPHRASE="YourSecurePassphrase123!" export BORG_RSH="ssh -i /home/backup-user/.ssh/id_rsa"

Logging

LOG_FILE="/var/log/backups/system-backup.log" exec 1> >(tee -a "$LOG_FILE") exec 2>&1 echo "========== Backup started: $(date) =========="

Backup paths to include

BACKUP_PATHS=" /etc /home /var/log /opt /root /usr/local "

Paths to exclude

EXCLUDE_PATTERNS=" --exclude /home/*/.cache --exclude /home/*/.local/share/Trash --exclude /var/log/journal --exclude /var/cache --exclude /tmp --exclude '*.tmp' --exclude '*.log.gz' --exclude '.log.[0-9]' --exclude 'lost+found' "

Create backup with current timestamp

ARCHIVE_NAME="system-{now:%Y-%m-%d_%H:%M:%S}" echo "Creating archive: $ARCHIVE_NAME" borg create \ --verbose \ --filter AME \ --list \ --stats \ --show-rc \ --compression lz4 \ $EXCLUDE_PATTERNS \ "::$ARCHIVE_NAME" \ $BACKUP_PATHS backup_exit=$? echo "Backup completed with exit code: $backup_exit"

Prune old backups (retention policy)

echo "Pruning old archives" borg prune \ --list \ --prefix system- \ --show-rc \ --keep-daily 7 \ --keep-weekly 4 \ --keep-monthly 6 \ --keep-yearly 1 prune_exit=$? echo "Prune completed with exit code: $prune_exit"

Compact repository

echo "Compacting repository" borg compact compact_exit=$? echo "Compact completed with exit code: $compact_exit"

Global exit status

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 echo "========== Backup completed successfully: $(date) ==========" else echo "========== Backup completed with errors: $(date) ==========" fi echo "" exit $global_exit

Set script permissions

Make the backup script executable and set secure permissions. Only the backup user should be able to read the script containing the passphrase.

sudo chmod 750 /opt/backups/scripts/system-backup.sh
sudo chown backup-user:backup-user /opt/backups/scripts/system-backup.sh
Security: Never use chmod 777 on backup scripts. The script contains sensitive credentials and should only be readable by the backup user.

Create environment file

Store environment variables in a separate file for the systemd service. This keeps credentials separate from the script.

BORG_REPO=/opt/backups/repos/system-backup
BORG_PASSPHRASE=YourSecurePassphrase123!
PATH=/usr/local/bin:/usr/bin:/bin

Secure environment file

Set restrictive permissions on the environment file to protect credentials.

sudo chmod 600 /opt/backups/scripts/backup.env
sudo chown backup-user:backup-user /opt/backups/scripts/backup.env

Create systemd service unit

Define the systemd service that will execute the backup script. This runs as the backup user and loads the environment file.

[Unit]
Description=System Backup with BorgBackup
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
User=backup-user
Group=backup-user
EnvironmentFile=/opt/backups/scripts/backup.env
ExecStart=/opt/backups/scripts/system-backup.sh
StandardOutput=journal
StandardError=journal
SyslogIdentifier=system-backup

Security hardening

NoNewPrivileges=yes ProtectSystem=strict ProtectHome=read-only ReadWritePaths=/opt/backups /var/log/backups PrivateTmp=yes ProtectKernelTunables=yes ProtectControlGroups=yes RestrictSUIDSGID=yes

Create systemd timer unit

Create the timer that will trigger the backup service. This example runs backups daily at 2 AM with randomization to avoid system load spikes.

[Unit]
Description=Run system backup daily
Requires=system-backup.service

[Timer]

Run daily at 2 AM with 30 minute random delay

OnCalendar=daily RandomizedDelaySec=30min Persistent=true

Prevent multiple instances

AccuracySec=1h [Install] WantedBy=timers.target

Enable and start the timer

Reload systemd configuration and enable the timer to start automatically. The timer will persist across reboots.

sudo systemctl daemon-reload
sudo systemctl enable system-backup.timer
sudo systemctl start system-backup.timer

Create backup monitoring script

Create a script to monitor backup status and send alerts if backups fail or become outdated.

#!/bin/bash

Backup monitoring script

set -e

Configuration

export BORG_REPO="/opt/backups/repos/system-backup" export BORG_PASSPHRASE="YourSecurePassphrase123!" LOG_FILE="/var/log/backups/backup-monitor.log" MAX_AGE_HOURS=26 # Alert if last backup is older than 26 hours ALERT_EMAIL="admin@example.com"

Logging

exec 1> >(tee -a "$LOG_FILE") exec 2>&1 echo "========== Backup Monitor Check: $(date) =========="

Get last backup info

LAST_ARCHIVE=$(borg list --short --last 1 | head -n1) if [ -z "$LAST_ARCHIVE" ]; then echo "ERROR: No backups found in repository" echo "No backups found" | mail -s "Backup Alert: No backups found" $ALERT_EMAIL 2>/dev/null || echo "Failed to send email alert" exit 1 fi echo "Last backup archive: $LAST_ARCHIVE"

Check backup age

ARCHIVE_INFO=$(borg info "::$LAST_ARCHIVE" --json) LAST_BACKUP_TIME=$(echo $ARCHIVE_INFO | python3 -c "import sys, json; print(json.load(sys.stdin)['archives'][0]['start'])") LAST_BACKUP_TIMESTAMP=$(date -d "$LAST_BACKUP_TIME" +%s) CURRENT_TIMESTAMP=$(date +%s) AGE_HOURS=$(( (CURRENT_TIMESTAMP - LAST_BACKUP_TIMESTAMP) / 3600 )) echo "Last backup was $AGE_HOURS hours ago" if [ $AGE_HOURS -gt $MAX_AGE_HOURS ]; then echo "WARNING: Last backup is older than $MAX_AGE_HOURS hours" echo "Last backup was $AGE_HOURS hours ago" | mail -s "Backup Alert: Backup overdue" $ALERT_EMAIL 2>/dev/null || echo "Failed to send email alert" exit 1 fi

Check repository integrity (weekly)

DAY_OF_WEEK=$(date +%u) if [ $DAY_OF_WEEK -eq 7 ]; then # Sunday echo "Running weekly repository check" borg check --repository-only if [ $? -eq 0 ]; then echo "Repository integrity check passed" else echo "ERROR: Repository integrity check failed" echo "Repository check failed" | mail -s "Backup Alert: Repository corrupted" $ALERT_EMAIL 2>/dev/null || echo "Failed to send email alert" exit 1 fi fi echo "Backup monitoring completed successfully" echo "========== Monitor Check Complete: $(date) =========" echo "" exit 0

Set monitoring script permissions

Make the monitoring script executable and secure.

sudo chmod 750 /opt/backups/scripts/backup-monitor.sh
sudo chown backup-user:backup-user /opt/backups/scripts/backup-monitor.sh

Create monitoring systemd units

Create systemd service and timer for backup monitoring that runs every 6 hours.

[Unit]
Description=Backup Monitoring Check
After=network-online.target

[Service]
Type=oneshot
User=backup-user
Group=backup-user
EnvironmentFile=/opt/backups/scripts/backup.env
ExecStart=/opt/backups/scripts/backup-monitor.sh
StandardOutput=journal
StandardError=journal

Create monitoring timer

Create the timer for backup monitoring checks.

[Unit]
Description=Run backup monitoring every 6 hours
Requires=backup-monitor.service

[Timer]
OnCalendar=--* 00,06,12,18:00:00
Persistent=true

[Install]
WantedBy=timers.target

Enable monitoring timer

Enable and start the monitoring timer.

sudo systemctl daemon-reload
sudo systemctl enable backup-monitor.timer
sudo systemctl start backup-monitor.timer

Create backup restore script

Create a utility script for easy backup restoration when needed.

#!/bin/bash

Backup restoration script

set -e

Configuration

export BORG_REPO="/opt/backups/repos/system-backup" export BORG_PASSPHRASE="YourSecurePassphrase123!" if [ $# -ne 2 ]; then echo "Usage: $0 " echo "Example: $0 system-2024-01-15_02:30:15 /tmp/restore" echo "" echo "Available archives:" borg list --short exit 1 fi ARCHIVE_NAME="$1" RESTORE_PATH="$2" echo "Restoring archive '$ARCHIVE_NAME' to '$RESTORE_PATH'" echo "This may take some time..."

Create restore directory

mkdir -p "$RESTORE_PATH"

Extract archive

borg extract --verbose "::$ARCHIVE_NAME" --destination "$RESTORE_PATH" echo "Restore completed successfully" echo "Files restored to: $RESTORE_PATH" ls -la "$RESTORE_PATH"

Set restore script permissions

Make the restore script executable.

sudo chmod 750 /opt/backups/scripts/restore-backup.sh
sudo chown backup-user:backup-user /opt/backups/scripts/restore-backup.sh

Verify your setup

Check that all components are working correctly and view backup status.

# Check timer status
sudo systemctl status system-backup.timer
sudo systemctl status backup-monitor.timer

List active timers

sudo systemctl list-timers | grep backup

Run a test backup manually

sudo systemctl start system-backup.service

Check service logs

sudo journalctl -u system-backup.service -f

View backup repository info

sudo -u backup-user borg info /opt/backups/repos/system-backup

List available archives

sudo -u backup-user borg list /opt/backups/repos/system-backup
Note: The first backup will take the longest time as it creates the initial archive. Subsequent backups will be much faster due to deduplication.

Advanced configuration

Configure remote backup storage

For production environments, consider backing up to remote storage. Here's how to configure SSH-based remote backups.

# Generate SSH key for backup user
sudo -u backup-user ssh-keygen -t rsa -b 4096 -f /opt/backups/.ssh/id_rsa -N ""

Copy public key to remote server

sudo -u backup-user ssh-copy-id backup@remote-server.example.com

Update backup script to use remote repository

export BORG_REPO="backup@remote-server.example.com:/backup/repos/system-backup"

Configure email notifications

Install mail utilities for backup alerts and notifications.

sudo apt install -y mailutils postfix
sudo dnf install -y mailx postfix

Common issues

SymptomCauseFix
"Permission denied" errorsIncorrect file ownership or permissionssudo chown -R backup-user:backup-user /opt/backups
"Repository not found" errorBORG_REPO path incorrect or not initializedCheck path and run borg init if needed
Timer not runningTimer not enabled or systemd not reloadedsudo systemctl daemon-reload && sudo systemctl enable --now system-backup.timer
Backup fails with passphrase errorWrong passphrase in environment fileCheck /opt/backups/scripts/backup.env
Large log files filling diskNo log rotation configuredConfigure logrotate for /var/log/backups/
Backup takes too longToo many files or wrong compressionAdjust exclude patterns or use faster compression

Backup retention and monitoring

Understanding and managing your backup retention policy is crucial for effective backup management. The configured retention policy keeps:

  • Daily backups for 7 days
  • Weekly backups for 4 weeks
  • Monthly backups for 6 months
  • Yearly backups for 1 year

Monitor disk usage regularly and adjust retention as needed. Use the monitoring script to ensure backups are running successfully and check for corruption weekly.

For comprehensive backup monitoring in production environments, consider integrating with system performance monitoring tools to track backup performance and resource usage.

Next steps

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle managed cloud infrastructure for businesses that depend on uptime. From initial setup to ongoing operations.