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
- Systemd services can run in the user space, if the
--userargument 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
.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
.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
- Enabled and start the timer with:
systemctl enable myunit.service systemctl start myunit.service
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 at
HH/FREQ:MIN: Run at
HH:MINand then at
HH+(FREQ*N):MIN. For instance
8/2:20runs every two hours starting at
HH1..HH2/FREQ:MIN: Run between
HH2, starting at
HH1:MINand repeating every
dailyrun every day at midnight
See the manual entries for
systemd.timers for more
detailed information about the syntax supported.
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.service systemctl --user start myunit.service
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: Started Backup important files for system configuration. Sep 04 11:26:32 qonos systemd: config-backup.timer: Succeeded. Sep 04 11:26:32 qonos systemd: Stopped Backup important files for system configuration. -- Reboot -- Sep 04 11:27:06 qonos systemd: Started Backup important files for system configuration. -- Reboot -- Sep 05 12:54:46 qonos systemd: Started Backup important files for system configuration. Sep 05 14:55:38 qonos systemd: config-backup.timer: Succeeded. Sep 05 14:55:38 qonos systemd: Stopped Backup important files for system configuration.
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.