Automating Borg Backups on macOS using Launch Agent

Mar 30, 2025

From Time Machine to Borg

Time machine is a longstanding macOS tool to create incremental backups of your entire hard drive. It has saved valuable data for me many times over the years that otherwise would have been lost, and the cosmic visualization makes it almost a joy to use. It can back up to a local drive or to network storage, and macOS can easily restore from it when setting up a new Mac. That said, it has a number of implementation quirks and feature shortcomings. The GUI “Enter Backup” view is laggy, and the backups will occasionally seem to stall . Like most Apple solutions, Time Machine is largely a black box, so even nerds are often stumped when it stops working. In addition, I’ve been accessing my Mac Mini more and more over a simple ssh interface, so I have been looking for a non GUI-backup tool. Ideally, one that runs on both mac and linux.

I have spent a lot of time reading r/selfhosted over the past year, and many users recommend Borg for backups. So I set it up on my Mac Mini home server and have been using it in place of Time Machine for the past month. It too has some implementation quirks, largely having to do with macOS restrictions. But on the whole I am quite happy with it and plan to keep it in place.

Borg is a “deduplicating archiver with compression and encryption”. Like Time Machine, it makes incremental backups that use only as much disk space as the files changed. It is a command line tool, though there are platform specific GUIs (eg, Vorta for Mac). Like many command line tools, Borg is versatile to a fault, allowing you to configure many options in each command. Luckily there is a python-based tool called Borgmatic which lets you setup backups declaratively using a single configuration file. Borgmatic provides a small set of high level commands which themselves call Borg using the options in the configuration file.

Initial Borg setup

We will set up Borg with homebrew, and install the FUSE filesystem driver to allow Borg to expose your backup data as a mounted filesystem. Unfortunately, the latter requires installing a kernel extension. After running these commands, follow the macOS prompts to install the kernel extension (see more details here ). We also install a pip dependency to allow posting to Telegram when backups fail.

brew install --cask macfuse
brew install borgbackup/tap/borgbackup-fuse
brew install borgmatic

# Install apprise in the borgmatic homebrew package,
# used to ping Telegram with backup notifications.
$(head -1 $(which borg) | sed 's/^#!//') -m pip install apprise

Generate an initial Borgmatic configuration file in our home directory.

borgmatic config generate --destination ~/.config/borgmatic/config.yaml

The important settings to modify are below. Quite a bit of documentation is provided in the default configuration file. There are also various settings to configure how long backups are kept, which (if any) hooks to run pre- and post- backup, and which services to ping with backup notifications (eg, Telegram).


# ~/.config/borgmatic/config.yaml

# List of directories to backup
source_directories:
    ...

# Add any local or remote repositories.
repositories:
    - path: /Volumes/your_backup_volume/borg
      label: your_backup_name

# Set to home directory to avoid needing sudo when running our Launch Agent
working_directory: ~

# List of directory patterns to exclude from backups
exclude_patterns:
    ...

# Password used to encrypt backup data
encryption_pass_phrase: ...

Initialize the backup repository using repokey encryption with the passphrase stored in the config file.

borgmatic init --encryption repokey

Run your first backup!

# Optionally  add argument --list to log each file as it is backed up
borgmatic --verbosity 0 --stats

Schedule backups with Launch Agent

Unfortunately scheduling automatic backups with Borg on macOS requires some tinkering (Alternatively, use the Vorta GUI). macOS provides a robust scheduling solution called launchd which lets you schedule “Launch Agent” scripts in a straightforward, highly debuggable manner. Ok that last part was a joke, but it does work well once you figure out how to do it.

In order to run our backup from a Launch Agent, we must wrap the borgmatic command in an app bundle, otherwise we are unable to give it proper disk access permissions. (See this , this , this , and this ). [1] Luckily it is easy to generate such a wrapper app from the command line, though we must run it first manually and and accept the requested permissions in the popup dialogs.

First create script to run the backup commands.

#!/bin/bash
#
# /Users/aneben/run_backup.sh

# Note: absolute paths must be used in this file

export PATH=/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

# Redirect output to borg.log
mkdir -p /Users/aneben/.log
exec > /Users/aneben/.log/borg.log 2>&1

# Manually break any lockouts in case past runs have been cancelled.
borg break-lock /Volumes/your_backup_volume/borg

# Run the backup
borgmatic --verbosity 0 --stats
osacompile -o ~/Applications/BackupWrapper.app -e 'do shell script "/bin/bash /Users/aneben/run_backup.sh"'

Now we can invoke this application from a Launch Agent. Generate the following file in ~/Library/LaunchAgents/, with a name like com.your_name.borg.plist

<!-- /Users/aneben/Library/LaunchAgents/com.your_name.borg.plist -->

<!-- Note: absolute paths must be used in this file -->

<?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.abraham.borg</string>

    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/open</string>
        <string>/Users/aneben/Applications/BackupWrapper.app</string>
    </array>

    <key>WorkingDirectory</key>
    <string>/Users/aneben/</string>

    <key>StartCalendarInterval</key>
    <dict>
        <!-- Run on the 15th minute of every hour. -->
        <key>Minute</key>
        <integer>15</integer>
    </dict>

    <!-- Also run once when the Launch Agent is loaded (typically after logging in). -->
    <key>RunAtLoad</key>
    <true/>

    <key>KeepAlive</key>
    <false/>

    <key>StandardErrorPath</key>
    <string>/Users/aneben/.log/borg.log</string>

    <key>StandardOutPath</key>
    <string>/Users/aneben/.log/borg.log</string>

    <key>EnvironmentVariables</key>
    <dict>
        <key>PATH</key>
        <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
    </dict>
</dict>
</plist>

Enabled the Launch Agent with

$ launchctl enable gui/501/com.your_name.borg

Restart your computer, then run the below command.

$ launchctl kickstart gui/501/com.abraham.borg

Access backed up data

As noted above, we have installed Borg with the FUSE file system drivers, allowing us to browse our Borg repository as a file system.

# Mount your borg repo as a filesystem
mkdir -p ~/mount/borg
borgmatic mount --mount-point ~/mount/borg

# Then unmount
borgmatic mount --mount-point ~/mount/borg

[1] I’ve seen various references to running scripts directly in Launch Agents (eg this ), but that has never worked for me.