Automatic backup with restic and systemd service

Magnetic hard disk open with the head and platter showing. Photo by Benjamin Lehman on Unsplash

Ever since I read the Automate backups with restic and systemd on Fedora Magazine, I have been meaning to practice the 3-2-1 backup strategy.

For a while, that did not come to fruition. Ultimately, that was down to laziness first of all; secondly, figuring out how and where the backups should be was also another struggle.

However, I finally sat down one weekend and figured out the works.

What is the 3-2-1 backup strategy?

The 3-2-1 strategy boils down to1:

  • 3 copies of data
  • 2 different media
  • 1 copy being off-site

The tutorial in the Fedora Magazine showcased how one can use restic2 and trigger the backup periodically via a systemd service unit file, instead of using cron. This also gave me the opportunity to learn a bit more about creating systemd service and timer units, so I decided to follow this.

However, the tutorial made use of an offsite backup solution provided by BackBlaze B2 Cloud storage. That solution was not one I was comfortable using just yet.

Mainly due to not wanting to use a service that did not provide Unix/Linux native tools, such as ssh, rsync and or sftp capabilities.

Therefore, I came up with these requirements:

  • 1 x local LAN server backup
  • 1 x local external drive backup
  • 1 x Unix/Linux native tool capable offsite backup

LAN server backup

This one was pretty easy, I already had a desktop computer which I use as a Virtual Machine host, that was connected via tailscale and had a lot of disk space using traditional hard disks.

So all I had to do was initialise a folder on this host via restic using SFTP as described in: https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html#sftp

Problem #1 solved!

Local drive backup

My laptop is connected to a dock and has a couple of USB-C. I also had a couple of external storage drives, especially the Samsung Portable SSD T7 - 1TB drive.

So all I had to do was make sure that the backup service runs if and only if this drive was mounted.

Problem #2 solved!

In fact, this already in some way, satisfies #1 + #2 of the 3-2-1 strategy.

However, I still needed to make the similar copy backed up offsite. As per my requirements #3, BackBlaze B2 was off the list, sadly. 🤕

A while back, I read an article titled Interview with John Kozubkik - John is the CEO of rsync.net which is a cloud storage in the form of a UNIX filesytem available over SSH. Ever since then I have been meaning to use this service somehow.

So for my #3 requirement, I researched using rsync.net and luckily enough, they have a special pricing for restic - https://rsync.net/products/restic.html

Not only that what makes rsync.net interesting is that:

there is no app or API - it simply gives you an empty UNIX filesystem accessible with any SSH tool.

P-e-r-f-e-c-t!

So let’s piece the things together.

Systemd service and timer units

Systemd services (ends in .service) can be triggered periodically by Systemd timer units (ends in .timer). This separates the actual command / script that does the work with the timer configuration. I consider this to be neat!

Furthermore, as pointed out by the great ArchLinux wiki3, the benefits are:

  • Jobs can be easily started independently of their timers. This simplifies debugging.
  • Each job can be configured to run in a specific environment (see systemd.exec(5)).
  • Jobs can be attached to cgroups.
  • Jobs can be set up to depend on other systemd units.
  • Jobs are logged in the systemd journal for easy debugging.

So how would this look like in practice?

Let’s say that your backup command is as follows:

restic backup /home/user/secret-stuff \
    --repo=sftp:backup1-host.rsync.net:backups \
    --verbose \
    --one-file-system \
    --exclude=/home/user/blah 

The systemd service file, let’s call it, offsite-backup.service would be:

[Unit]
Description=Offsite backup with restic

[Service]
Type=simple
Restart=on-failure
RestartSec=30
ExecStartPre=restic unlock
ExecStart=restic backup /home/user/secret-stuff \
    --repo=sftp:backup1-host.rsync.net:backups \
    --verbose \
    --one-file-system \
    --exclude=/home/user/blah
ExecStopPost=restic unlock

The offsite-backup.timer would be:

[Unit]
Description=Offsite backup with restic

[Timer]
OnCalendar=daily UTC
Persistent=true
RandomizedDelaySec=300

[Install]
WantedBy=timers.target

Repeat the above steps similarly for different configurations for different backup medium. For example, to backup locally to an external drive if and only if it is mounted, I have a service like so:

[Unit]
Description=Local Restic backup service
ConditionPathIsMountPoint=/run/media/user/BackupDriveName/

[Service]
Type=simple
Restart=on-failure
RestartSec=30
ExecStart=restic backup /home/user/secret-stuff \
    --repo=sftp:backup1-host.rsync.net:backups \
    --verbose \
    --one-file-system \
    --exclude=/home/user/blah

If you are running the services for yourself under your own user, you can put them in ~/.config/systemd/user/ folder, otherwise it will have to go into /etc/systemd/system/ or system-wide user units in /etc/systemd/user/.

After that first reload the systemd daemon so that it knows there are new services available. Note: For user services include the --user argument after systemctl.

systemctl daemon-reload

Enable the timer unit:

systemctl enable --now offsite-backup.timer

To check when is the next scheduled run of your service:

systemctl list-timers

💯 🔥 Now you have an automatic backup job that runs according to the schedule you have setup, meaning you can sleep soundly while it does its job. 😄 🎉

Maverick Kaung
Maverick Kaung
IT Security Enthusiast

Ye Myat “Maverick” Kaung is a highly motivated individual with a passion for security and open source software. Also an aspiring hacker and software engineer.