Using systemd Timers Instead of crontabs
The software systemd
is preinstalled on most Linux systems and is commonly
known for being the destroyer of worlds a constant cause of problems
a rather notoriously difficult system management daemon. I’d like to discuss
some reasons why both timers are essential to someone’s workflow as well as why
systemd should probably be chosen over cron implementations. As per usual, this
is implementation dependent and other software may suit your usecase better.
Timers
Timers are useful for running things such as daily backups, alarms, and any other automated system. Automation in general is useful because it removes the human aspect of things, and timer programs such as cron, atd, and systemd timer units can be incredibly useful for this.
An essential component to systemd that is often overlooked is it’s ability to
replace cron
, a timer tool that has a side effect of not running in a logind
session, and therefore can be used to escape resource restrictions and
permissions enforcements due to the way the daemon runs. This unfortunately is
something that happens with all daemons that are started outside of the
user’s systemd session. Because cron is a system-level daemon, it’s not started
in your session.
The Arch Wiki guide has a link here if you would like a more formal introduction to systemd timers.
As an example, we can set a weekly timer to run a program in a binaries directory:
# disco-party.timer
[Unit]
Description=Run our weekly disco-party program.
[Timer]
OnCalendar=weekly
[Install]
WantedBy=default.target
# disco-party.service
[Unit]
Description=Set up a Disco Party
[Service]
ExecStart=/home/%h/bin/disco-party
Type=oneshot
Since this is a user unit, you should use the default target (provided by systemd when a session is ready).
The OnCalendar unit can take the values listed here. While cron used a basic format, defining numbers in order of minute, hour, day, month, and year, systemd instead has a different format:
OnCalendar=[weekday] <day of month>-<year>-<year> <hour>:<minute>:<second>
Any value can be replaced with a star (except for weekday I think, which can be omitted in its entirety) to match “any” value, or a range can be provided. For example, to run a program on weekdays at 5 PM:
OnCalendar=Mon..Fri *-*-* 17:00:00
We can also set up a monotonic seconds-since timer, with various options such as OnActiveSec (seconds since timer was started), OnBootSec (seconds since the system booted), OnStartupSec (seconds since the session started, such as when a user logs in for the first time since reboot), OnUnitActiveSec (seconds since the unit was last activated), and OnUnitDeactiveSec (seconds since the unit was last deactivated).
An example unit, ripped straight out of shell-server:
# ansible-pull.timer
[Unit]
Description=Run ansible-pull every 15 mins
[Timer]
OnBootSec=15min
OnUnitDeactiveSec=15m
[Install]
WantedBy=timers.target
Since this is a system-level unit, you should use the timers.target unit, provided by systemd when timers are ready to be run.
Cron Run-Parts Imitation
Some cron
implementations have a @weekly
,@daily
, and similar time options
which closely resemble the OnCalendar=
option above. As such, we can
quickly produce something similar without using cron
. This does require the
Debian run-parts
command. source
# crontab.service
[Install]
WantedBy=multi-user.target
[Unit]
Description=Simulates cron, limited to /etc/cron.*
Requires=crontab@hourly.timer
Requires=crontab@daily.timer
Requires=crontab@weekly.timer
Requires=crontab@monthly.timer
# crontab@.service
[Unit]
Description=%I job for /etc/cron.%I
RefuseManualStart=yes
RefuseManualStop=yes
ConditionDirectoryNotEmpty=/etc/cron.%I
[Service]
Type=oneshot
IgnoreSIGPIPE=no
WorkingDirectory=/
ExecStart=/bin/run-parts --report /etc/cron.%I
# crontab@.timer
[Unit]
Description=%I timer simulating /etc/cron.%I
PartOf=crontab.target
RefuseManualStart=yes
RefuseManualStop=yes
[Timer]
OnCalendar=%I
Persistent=yes
This would need to be a system-level unit, which can be enabled by running the
command sudo systemd enable --now crontab
. To avoid running in duplicate with
cron
it is recommended to only do this if cron
is masked, not just
disabled, as future updates can re-enable cron
. You can do this by running
sudo systemd mask cron
.
This article is a live post and will be updated if amendment is needed to clarify explanations of certain topics.
EDIT-2020-08-15: Rewrite to be less snarky.