I want to set up an easy backup system for my files, both locally and remotely.

Here is how I’ve set it up.

1. Requirements

I want to be able to make regular backups on a different host with the following requirements:

  • transparently send the backup to the destination host

  • the backup should not take any space on my main computer

  • de-duplicated data: do not send data to backup if it’s already there in a previous backup

  • encrypted and authenticated, so that it can be hosted on a remote host with no privacy concern

  • run in the background without interactions

  • backup program must be open source

2. Preparing the backup destination

I have a local NAS with 2 disks in mirror mode.

I’ve enabled the ssh server on it, and I’ve fixed the IP address of the NAS in my router to 10.0.0.10, because that’s an easy IP to remember.

Because I do not want to have to be there to authenticate the ssh connection, I’m sending my ssh public key to it:

ssh-copy-id user@10.0.0.10

To check, let’s ssh into it: ssh user@10.0.0.10, and the password should not required.

We can now mount this host locally thanks to sshfs so that the remote directory appears local:

mkdir ~/NAS && sshfs user@10.0.0.10:/share/Public/Shared ~/NAS

Now let’s ask systemd to auto mount it when my user is connected: let’s create a file named ~/.config/systemd/user/home-user-NAS.mount with the following content:

[Unit]
Description=Mount the NAS fs via sshfs

[Install]
WantedBy=multi-user.target

[Mount]
What=remote_user@10.0.0.10:/share/Public/Shared
Where=/home/user/NAS
Type=fuse.sshfs
Options=_netdev,IdentityFile=/home/user/.ssh/id_ed25519,reconnect,ServerAliveInterval=30,ServerAliveCountMax=5,x-systemd.automount,uid=1000,gid=100

And give it a try:

systemctl --user daemon-reload
systemctl --user start home-user-NAS.mount
systemctl --user status home-user-NAS.mount
systemctl --user enable home-user-NAS.mount

We now have a remote SSH server automatically mounted when login in.

3. Initializing the backup

Borg fills in all the requirements.

First, let’s tell Borg where to store the backup, and what the encryption password is via a backup_init.sh:

#!/bin/sh

export BORG_REPO=~/NAS/Backup/borg
export BORG_PASSPHRASE='strong_passphrase'

borg init --encryption=repokey "$BORG_REPO"

Then ask Borg to give you the encryption key used:

borg key export --paper ~/NAS/Backup/borg

Now store both the passphrase and the key in your password manager.

Let’s tell borg what to backup: ~/Documents and ~/Pictures in my case. Create a backup_run.sh:

#!/bin/sh

# Setting this, so the repo does not need to be given on the commandline:
export BORG_REPO=~/NAS/Backup/borg

# See the section "Passphrase notes" for more infos.
export BORG_PASSPHRASE='strong_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                         \
    --verbose                       \
    --filter AME                    \
    --list                          \
    --stats                         \
    --show-rc                       \
    --compression zstd,10           \
    --exclude-caches                \
    --exclude '/home/*/.cache/*'    \
    --exclude '/var/tmp/*'          \
                                    \
    ::'{hostname}-{now}'            \
    /home/user/Documents            \
    /home/user/Pictures

backup_exit=$?

info "Pruning repository"

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

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

prune_exit=$?

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

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

exit ${global_exit}

And run it once to check if it works: chmod +x backup_run.sh && ./backup_run.sh.

4. Backup schedule

Now that our backup command works, let’s schedule it to run every hour.

We can use systemd again for that.

First, let’s create the service to run only if the NAS was correctly mounted (~/.config/systemd/user/backup.service`) with the following content:

[Unit]
Description=Run Borg backup
RequiresMountsFor=/home/user/NAS

[Service]
Type=simple
ExecStart=/home/user/backup_run.sh

[Install]
WantedBy=multi-user.target

And then let’s run it once to see if it works: systemctl --user start backup and systemctl --user status backup.

Now create the timer (~/.config/systemd/user/backup.timer`) with the following content:

[Unit]
Description=Schedule Borg Backup every 1 hour
RefuseManualStart=no
RefuseManualStop=no

[Timer]
# Execute job if it missed a run due to machine being off
Persistent=true
# Run 30 minutes after boot for the first time
OnBootSec=1800
# Run on every hour thereafter
OnCalendar=*-*-* *:00:00
# File describing job to execute
Unit=backup.service

[Install]
WantedBy=timers.target

Enable both the service and the timer:

systemctl --user enable backup.service
systemctl --user enable backup.timer
systemctl --user start backup.timer

5. Off site copy

My NAS allows me to sync a local directory to different cloud storage providers. So this part really depends on your possibilities.

6. Conclusion

Borg allows a relatively simple and well encrypted backup system. Paired with systemd timers, it makes having backup relatively simple for a Linux system.