You Don't Love Systemd Timers Enough

birdculture1 pts0 comments

Tyblog | You Don't Love systemd Timers Enough

You Don't Love systemd Timers Enough

5 May, 2026

2,139 words

9 minute read time

Figure 1: Plato's Cave by Jan Pietersz Saenredam; 24 hour clock licensed under CC3 from Wikimedia; systemd logo by the systemd project licensed under CC-BY-SA 4.0

My favorite metonymic technology term is "cron job": even though cron may not literally be the daemon that executes actions on a schedule, we apply the term to anything that walks like a cron and quacks like a cron.<br>As Patrick McKenzie likes to point out, cron jobs are one of the most eminently useful computing primitives.<br>They offer utility that's almost immediately obvious for plenty of use cases that almost everybody has: do this every day; do that once a month.

And yet.<br>You probably shouldn't use literal cron (or its more modern cousins) for scheduled tasks!<br>In 2026 there are more modern options available, and my favorite is the humble systemd timer.<br>I love systemd timers.<br>If you don't love them yet, maybe I can show you the reasons why you should love them, too.

My cron? Cooked?

A systemd timer is a type of unit that schedules other units (usually a service) on a particular schedule.<br>(How a systemd service unit works is another article, but you can logically consider the .service target of a systemd timer to be a script.)<br>Timers are effectively a functional replacement for a traditional cron daemon (though you could conceivably run both), and timer calendar settings offer some similarities to help bridge the gap from traditional cron-like expressions.

At this point the systemd haters peer out of the woodwork in anticipation of torpedoing timers because they are part of the systemd project and because they replace mature (if clunky) technology.<br>I'd rather not spend our time arguing about cron, so briefly consider why newer solutions like systemd timers that benefit from years of hindsight are better:

Ambiguous $PATH settings make cron script execution difficult to predict.

stdout and stderr output often ends up in a black hole (and, often, sent to the host's mail system, which is usually not what you want to happen.)

Execution history is difficult to follow and interrogate.

You might feel cool knowing the scheduling grammar by heart, but 01,31 04,05 1-15 1,6 * isn't easy or intuitive for humans to read.

Incidentally, timers solve all these problems (and more.)

Prime Time for a Timer Primer

We can cover the basics without a lot of ceremony.<br>First you need a target for a timer to execute.<br>On a Linux host with systemd operational, placing the following unit contents at /etc/systemd/system/roulette.service installs a service with a 1 in 10 chance to be free (i.e., shut down your computer):

SystemdFont used to highlight strings.

Font used to highlight keywords.

Font used to highlight type and class names.

[Unit]<br>Description=1 in 10 chance to break your chains

[Service]<br>ExecStart=/usr/bin/env bash -c '[[ $(($RANDOM % 10)) == 0 ]] && systemctl poweroff || echo LIVE ANOTHER DAY'

Update: [2026-05-05 Tue]

Twitter mutual HSVSphere points out that the service option ExecCondition= offers a native way to handle conditional execution.<br>This is a more tightly-integrated way to express "should I continue to execute?" and I agree that it offers a clearer way to express intent at the unit level (I'm using absolute paths here for a NixOS system):

SystemdFont used to highlight strings.

Font used to highlight keywords.

Font used to highlight type and class names.

[Unit]<br>Description=1 in 10 chance to break your chains

[Service]<br>ExecCondition=/run/current-system/sw/bin/bash -c '[[ $(($RANDOM % 10)) == 0 ]]'<br>ExecStart=/run/current-system/sw/bin/systemctl poweroff

This has the same effect as the prior bash conditional, and you end up with different wording in the journal that (in my opinion) expresses the situation more clearly for you when the condition is met:

May 05 11:05:32 diesel systemd[3117]: Condition check resulted in 1 in 10 chance to break your chains being skipped.

In general, leaning into the options that systemd presents is a better experience than scripting your own. (Another example would be to use OnFailure= to react when your service scripts fail or Restart= to attempt recovery in the case of ephemeral failures.)

Associate that service with a timer by placing a file with the same file stem (roulette) at /etc/systemd/system/roulette.timer:

SystemdFont used to highlight keywords.

Font used to highlight type and class names.

[Unit]<br>Description=impending destruction

[Timer]<br>OnCalendar=10:00

[Install]<br>WantedBy=timers.target

What I mean by associate is that, by default, a timer's Unit= setting will choose a service unit with a matching stem suffixed by .service.<br>In this case, roulette.service.<br>You can always change this if you want to execute a service with a different unit name.

I want to call out a few things right away:

Per normal service unit semantics, the ExecStart= target does not run...

systemd service cron unit timer timers

Related Articles