I put together an automatic hourly backup of selected directories using rsync and a systemd timer. It was a little bit more of a hassle than I anticipated, given my complete unfamiliarity with systemd, so I thought I’d document it for my own records and in case it’s useful to someone else on a distribution with systemd who has a similar use-case to mine.

Why a systemd timer and not cron?

In my case, mostly because I already have systemd and the timer comes with it, while I’d have to install cron. Evidently a systemd timer also has some advantages over cron such as easy debugging and flexibility. The Arch Wiki lists some benefits and caveats of using the systemd timer as a cron replacement.

Step 1: Set up the backup script

I used rsync for the backup script. While I’m not ssh-ing into an external machine and just using an external hard drive, it’s simple yet robust enough for this use-case, too. My backup script file is just a shell script that starts with lines like:

1
2
3
4
5
6
7
8
#!/bin/sh

# Backup documents
rsync -a /home/ljwrites/Documents/ /external/hdd/Documents/
# Backup pictures
rsync -a /home/ljwrites/Pictures/ /external/hdd/Pictures/
# Backup config folder
rsync -a /home/ljwrites/.config /external/hdd/Programs/

As you may know if you have some familiarity with rsync, the trailing slash on the source is significant. Without the trailing slash, the files in the source directory are copied into a subdirectory of the destination. This means that, in the above example, files and diretories from the Documents and Pictures directories are backed up to the Documents and Pictures directories of the external drive. The files and directories in .config, on the other hand, are backed up to Programs/.config in the external drive.

This file was made executable with chmod +x and lives in my ~/.local/bin with the file name backup. Yeah, I guess it should be backup.sh, but without the extension it’s shorter to run manually from the command line and it works. I am told the more proper way to do this is by creating a symlink named backup to my actual .sh file, but I don’t have a separate coding or development directory and I’m lazy lol.

Oh, and of course, to call backup in ~/.local/bin directly from the command line I had to add ~/.local/bin to my $PATH, which I did with a line in .bashrc like this:

1
PATH=$HOME/.local/bin:$HOME/go/bin:$PATH

I put ~/.local/bin and ~/go/bin ahead of $PATH rather than appending them, btw, because this way my local executables take precedence over the system ones. If there happens to be a binary called backup in the system files, for instance, when I type backup in the terminal I’ll be getting the backup script I wrote. This was another piece of advice from my dev friend, one that I actually took.

Once the backup script is tested and confirmed to be working, it’s time to set up the systemd service and timer.

Step 2: Set up the systemd service and timer

I wrote a filebackup.service text file that consists of just the following lines:

[Unit]
Description=File backup service

[Service]
ExecStart=/home/ljwrites/.local/bin/backup

You can set the description to whatever text string you like, and ExecStart is obviously a call to execute the backup file I put in .local/bin.

And here’s my filebackup.timer file:

[Unit]
Description=Hourly backup of files

[Timer]
OnCalendar=Hourly
AccuracySec=1s
Persistent=false
Unit=filebackup.service

[Install]
WantedBy=timers.target

OnCalendar=Hourly sets the hourly interval, and Unit calls the filebackup.service from above. I have no idea if timers.target is the best timer for this but it works. There’s a more detailed explanation of timer types on (where else?) Stack Exchange, so check up on that if you’re interested. According to that explanation it looks like basic.target would have worked, too.

I moved both the service and timer files to the /etc/systemd/system directory.

Step 3: Activate the systemd timer

You can check the list of timers with:

systemctl list-timers

You can include inactive timers with the --all flag:

systemctl list-timers --all

filebackup.timer shouldn’t be on the list yet. You can add it with:

sudo systemctl enable --now filebackup.timer

Check timer list again and it should be there, with the NEXT and LEFT columns set to the start of the next hour.

Update 2021/6/15: If the NEXT and LEFT columns of the backup timer show up as n/a, it may help to restart it using the following command:

sudo systemctl restart filebackup.timer

Step 4: Check if the backup ran with systemd journal

You can check if the backup timer is running on schedule with:

sudo journalctl -u filebackup.*

I’ve set this to an alias because I like the peace of mind in knowing I have regular backups of my stuff.

1
alias backupcheck='sudo journalctl -u filebackup.*'

And that’s it!

There you have it, my file backup setup. It’s really simple and basic, but does what I need it to do. It also took me a while to get working properly because I couldn’t find specific step-by-step instructions for my particular needs. Maybe it’ll help others who have similar use-cases but are not devs.