Arguably the most important bit of information about timers is how to express a schedule, whether a repeating period of time (which the manual usually refers to as a time span) versus a calendar event (or a timestamp).
Fortunately, I think the man page for this under systemd.time(7) is actually very good with plenty of examples.
You should use it as the first resource when writing timers; it's good (or better) than, uh, casual blog posts by casual writers.
systemd also ships with a command-line tool called systemd-analyze which includes the ability to validate and explain time expressions from the command line directly in an imperative way to help understand them.
You can even disambiguate the classic wildcard cron expression which systemd-analyzer can parse and then explain to you, complete with the expected execution times:
systemd-analyze calendar '*-*-* *:*:*'
Normalized form: *-*-* *:*:*Next elapse: Sat 2026-04-18 16:44:26 MDT(in UTC): Sat 2026-04-18 22:44:26 UTCFrom now: 431ms left
This blog post is not the place to reproduce the entirety of systemd.time(7) verbatim, so I encourage you to Read The Helpful Manual (RTHM).
Writ small, you can pretty simply define either a recurring wallclock period or, in contrast to plain old cron, a recurring period of time against some previous event.
The first category of time expressions is easy to envision.
For example, in fully-qualified form, daily means:
*-*-* 00:00:00│ │ │ │ │ ╰── at second 00│ │ │ │ ╰───── at minute 00│ │ │ ╰──────── at hour 00│ │ ╰────────── every day│ ╰──────────── every month╰────────────── every year
You can use shorthand terms like daily, write out the complete form, or use any other supported value listed out in systemd.time(7) and subsequently validate your assumptions against systemd-analyze.
The second category of time expressions apply to "run this relative to some other event."
This distinction from "run at the same time very day" is very often what you actually want.
Consider a job that clears out a temporary directory, for example: if a cron expression lapsed right after boot, there probably isn't much to clean out of /tmp at all.
But if you encode "execute an hour after my computer has started and then every hour after that", the schedule logic is meaningful for what the related service is actually doing.
This is easy to do in a timer:
Systemd- Font used to highlight keywords.
- Font used to highlight type and class names.
[Timer]
OnBootSec=1h
OnUnitActiveSec=1h
That is: "run an hour after the machine starts" (which will execute once) and also "run one hour after my Unit= runs" (which implicitly makes the timer repeat indefinitely.)
Periodic time spans like this fit the "every once in a while" use case surprisingly more often than "run at this minute every hour" and similar expressions.
Another good example is a timer I use every December to poll the Advent of Code API for a Slack bot I wrote for some friends.
The */15 cron expression honors the "every 15 minutes" policy that their API requests, but since that's the easiest way to express it in cron language, I'm sure it makes spiky traffic alongside everyone else polling the API!
Starting my timer when I've made a code fix that runs whenever 15 minutes has lapsed is all I care about, and probably creates less of a thundering herd problem.
Calendar versus time span units is probably the biggest conceptual leap from a traditional cron job, but timers offer more, too.