Systemd as a Cron replacement
Despite its very simple and effective syntax, installing new tasks in cron can be tricky. In fact:
- Cron tasks run in an environment different from that available in an interactive shell and a script running ok in a terminal might fail when installed as a cron job.
- As a consequence of the first point, the simplest way to test a cron job is installing it in cron and see what happens1. However, the maximum speed at which we can test changes to a cron task is once per minute, that is, the maximum frequency at which cron can schedule a task.
- Least but not last, cron uses a single log for all its tasks and analyzing the output produced by a failing cron job can be difficult to reconstruct.
There are alternatives to cron of which the simplest (in terms of installations, at least) is using systemd (see, e.g., Systemd as a cron replacement). So after many, many years with cron, I recently decided to try and switch my cron tasks to systemd.
The migration was mostly painless. I had to overcome a bit of friction to adapt to the new syntax fo calendar expressions, but it did not take take much time and I am now very happy with the new setup.
There is quite some documentation on the steps to perform the switch, with the Arch Wiki providing, as in many other cases, precise and simple guidelines. See, for instance, systemd/Timers, Systemd/User, and Systemd.
Here I recap the main steps and expand on the points which caused my the most difficulties.
Basic information about systemctl --user
- Systemd services can run in the user space, if the
--user
argument is specified. Each service is specified with a file stored in.config/systemd/user
; there is no “installation” of the file: you just write it there. - A systemd timer specifies how often a specific service has to be run. It
is specifide with a file in
.config/systemd/user
, which references a service in the same directory. - Timers have to be enabled and started, while the services referenced by a timer do not.
Creating a Timer
- Create a service file
myunit.service
in.config/systemd/user
, which specifies the script(s) to run. For instance:
[Unit] Description=Config Backup Service [Service] Type=oneshot ExecStart=/home/adolfo/Sources/bash/pacman-list.bash ExecStart=/home/adolfo/Sources/bash/systemd-backup.bash
- Create a timer file
myunit.timer
in.config/systemd/user
, which specifies how often the service has to run. For instance, we decide to run the service at 9:10am:
[Unit] Description=Backup important files for system configuration [Timer] OnCalendar=9:10 Persistent=true [Install] WantedBy=timers.target
- Enable and start the timer with:
systemctl enable --user myunit.timer systemctl start --user myunit.timer
You can omit the --user
option if you want to enable the service at
the system level, that is, as root.
Frequency specification
The OnCalendar
section specifies the frequency at which a task has to be
run. The syntax is rather articulated. Some simple examples include:
HH:MM:SS
: Run every day atHH:MM:SS
HH/FREQ:MIN
: Run atHH:MIN
and then atHH+(FREQ*N):MIN
. For instance8/2:20
runs every two hours starting at8:20
.HH1..HH2/FREQ:MIN
: Run betweenHH1
andHH2
, starting atHH1:MIN
and repeating everyFREQ
hours.daily
run every day at midnight
See the manual entries for systemd.timer
and systemd.timers
for more
detailed information about the syntax supported.
Useful Commands
Check a calendar expression
systemd-analyze --iterations 2 calendar *-*-* Original form: *-2-29 Normalized form: *-02-29 00:00:00 Next elapse: Thu 2024-02-29 00:00:00 CET (in UTC): Wed 2024-02-28 23:00:00 UTC From now: 3 years 5 months left Iter. #2: Tue 2028-02-29 00:00:00 CET (in UTC): Mon 2028-02-28 23:00:00 UTC From now: 7 years 5 months left
Run a service (e.g., for testing purposes)
systemctl --user start myunit.service
Install a timer
systemctl --user enable myunit.timer systemctl --user start myunit.timer
List the timers which will run next
systemctl --user list-timers NEXT LEFT LAST PASSED UNIT ACTIVAT> Sat 2020-09-05 17:30:00 CEST 11min left Sat 2020-09-05 16:30:42 CEST 48min ago borg-local-backup.timer borg-lo> Sat 2020-09-05 18:00:00 CEST 41min left Sat 2020-09-05 16:00:42 CEST 1h 18min ago borg-backup.timer borg-ba> Sun 2020-09-06 09:10:00 CEST 15h left Sat 2020-09-05 11:42:44 CEST 5h 36min ago config-backup.timer config-> 3 timers listed.
View the output generated by a unit
journalctl -q --user-unit config-backup.timer -- Logs begin at Mon 2020-07-20 09:03:57 CEST, end at Sat 2020-09-05 16:50:52 CEST. -- Sep 04 08:42:41 qonos systemd[1096]: Started Backup important files for system configuration. Sep 04 11:26:32 qonos systemd[1096]: config-backup.timer: Succeeded. Sep 04 11:26:32 qonos systemd[1096]: Stopped Backup important files for system configuration. -- Reboot -- Sep 04 11:27:06 qonos systemd[1071]: Started Backup important files for system configuration. -- Reboot -- Sep 05 12:54:46 qonos systemd[1134]: Started Backup important files for system configuration. Sep 05 14:55:38 qonos systemd[1134]: config-backup.timer: Succeeded. Sep 05 14:55:38 qonos systemd[1134]: Stopped Backup important files for system configuration.
Footnotes:
This is not completely true, as one could set an environment which
simulates that of cron, for instance by running env
as a cron task.