~ systemd.timer, an alternative to cron
Posted on Thu 8 Dec 2022 to ProgrammingThere will come a point in time during your time administering a Linux server
where you will want to perform a job on a schedule. Perhaps you want to rotate
some TLS certificates before they expire, or delete old files that are no longer
needed. Typically for this, you would use cron,
perhaps the most widely used job scheduler for UNIX like systems. You would fire
up the crontab for the user, punch in the schedule followed by the command, then
write and quit. If you wanted to monitor the job, then you would add MAILTO
at the top to receive the cron logs should the job fail.
systemd offers an alternative to cron via systemd.timer, one that I
prefer over cron for reasons I will get into later. With systemd.timer, you
specify a *.timer
file and a corresponding *.service
, what with the latter
being the job you want to perform. For example, using the example of certificate
rotation, we might have a certrotate.service
file that looks like this,
[Unit]
Description=Rotate TLS certificates
[Service]
Type=oneshot
ExecStart=/usr/local/bin/uacme -d /etc/uacme.d -h /usr/local/share/uacme/uacme.sh issue example.com
and the corresponding certrotate.timer
file,
[Unit]
Description=Weekly rotation of TLS certificates
[Timer]
OnCalendar=weekly
# Set to true so we can store when the timer last triggered
# on disk.
Persistent=true
with both of these in place we can then start the timer.
$ sudo systemctl start certrotate.timer
The above is certainly more verbose than cron, what with the main difference being that two files are required, one for the job itself, and another for the timer. The implementation of timers in systemd offers up the following benefits over that of cron:
- It allows for independent job execution via
systemctl start
- Jobs can be configured to have dependencies
- Job output will automatically be written to systemd-journald
- Templated unit files
On the first point, you can make the argument that this is possible via cron. Since cron does just execute the arbitrary commands, so you could run the same commands in the crontab via the terminal. This is true, however systemd offers up some convenience in this regard. With the above example for certificate rotation I can fire of the job like so,
$ sudo systemctl start certrotate
without having to memorise the full sequence of flags and arguments to pass.
This makes debugging jobs easier, and should one fail you can check the status
via systemctl status
, or the entire log with journalctl -u
.
With the second point, since jobs are just regular *.service
files, they can
be configured to have dependencies via Wants
and Requires
, more details can
be found under systemd.unit. This can allow for more sophisticated
orchestration between jobs on a system, should any of them depend on another
job being completed first.
Regarding the final point, systemd offers the ability to have templated unit
files via specifiers. This reduces the overhead of managing jobs that have
repetitive logic. With the above example we currently only rotate the
certificate for the domain example.com
, what if we also want to rotate the
certificates for any subdomains too? First we would rename both the *.timer
and *.service
files so a @
precedes the suffix, so certrotate@.timer
,
and certrotate@.server
. Then, in certrotate@.service
we would modify
ExecStart
to use %i
for the instance name of the service,
ExecStart=/usr/local/bin/uacme -d /etc/uacme.d -h /usr/local/share/uacme/uacme.sh issue %i
then we can perform the following to have the changes applied,
$ sudo systemctl daemon-reload
$ sudo systemctl start certrotate@example.com.timer
and this would be the same for each subsequent domain too.
$ sudo systemctl start certrotate@api.example.com.timer
$ sudo systemctl start certrotate@about.example.com.timer
And my favourite thing about systemd.timer is how it provides and easy overview of the jobs running on your server, and when they will next execute,
$ systemctl list-timers
NEXT LEFT LAST PASSED UNIT ACTIVATES
Fri 2022-12-09 00:00:00 UTC 4h 57min left Thu 2022-12-08 00:00:00 UTC 19h ago logrotate.timer logrotate.service
Fri 2022-12-09 00:00:00 UTC 4h 57min left Thu 2022-12-08 00:00:00 UTC 19h ago man-db.timer man-db.service
Fri 2022-12-09 06:06:34 UTC 11h left Thu 2022-12-08 06:06:43 UTC 12h ago apt-daily-upgrade.timer apt-daily-upgrade.service
Fri 2022-12-09 08:19:01 UTC 13h left Thu 2022-12-08 18:34:00 UTC 28min ago apt-daily.timer apt-daily.service
Fri 2022-12-09 09:55:30 UTC 14h left Thu 2022-12-08 09:55:30 UTC 9h ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Now, one thing that systemd.timer lacks is the MAILTO
functionality offered by
cron. But this can be easily reimplemented by implementing another oneshot
service that is invoked via OnFailure
. For further information on this, see
the MAILTO section from the Arch wiki on how to achieve this.
If you are someone who administers a Linux server, one that uses systemd
specifically, I encourage you to invest some time in using systemd timers.
Sure, they're more verbose than cron, but I've found the tradeoff in that
regard to be one worth making. They're easy to manage, since they are just
standalone services at the end of the day, make deduplication of jobs a cinch
with unit specifiers, and offer easy monitoring via systemctl list-timers
as
shown above.