cron Cheatsheet

systemd Timers

Use this cron reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

A complete timer plus service pair

A systemd timer never carries the command. It points at a unit of the same name with a .service suffix, and that unit does the work. Two files, both in /etc/systemd/system/.

backup.service:

[Unit]
Description=Nightly backup
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
User=deploy
Environment=PATH=/usr/local/bin:/usr/bin:/bin
WorkingDirectory=/srv/app
ExecStart=/srv/app/bin/backup.sh

backup.timer:

[Unit]
Description=Run nightly backup at 02:30

[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
RandomizedDelaySec=5m
AccuracySec=1s
Unit=backup.service

[Install]
WantedBy=timers.target

Enable and start it, then confirm:

sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers backup.timer

Unit= is optional when the names match. There is no [Install] section in the service, because the timer is the thing that gets enabled. A Type=oneshot service is expected to exit, so systemd does not treat that exit as a failure.

OnCalendar syntax

The full form is DayOfWeek Year-Month-Day Hour:Minute:Second, and any leading part can be omitted. Fields accept *, lists with ,, ranges with .., and steps with /.

ExpressionMeaning
*-*-* 02:30:00every day at 02:30
Mon..Fri 09:00weekdays at 09:00
Mon,Thu *-*-* 18:00Mondays and Thursdays at 18:00
*-*-01 04:00first of every month at 04:00
*-*-~01 23:00last day of every month at 23:00
*-*-* *:0/15every 15 minutes
*-*-* 09..17:00on the hour from 09:00 to 17:00
2027-01-01 00:00:00once, at that instant
*-*-* *:*:00 UTCevery minute, in UTC

Shorthands expand to the same thing:

ShorthandExpands to
minutely*-*-* *:*:00
hourly*-*-* *:00:00
daily*-*-* 00:00:00
weeklyMon *-*-* 00:00:00
monthly*-*-01 00:00:00
yearly*-01-01 00:00:00
quarterly*-01,04,07,10-01 00:00:00
semiannually*-01,07-01 00:00:00

The trailing seconds field is optional and defaults to 00, so 02:30 and 02:30:00 are the same. A timezone name or UTC may follow the time. Multiple OnCalendar= lines in one timer are additive, and a single empty OnCalendar= clears every value set earlier.

Always check an expression before shipping it. This is the single most useful command in the whole timer story.

systemd-analyze calendar 'Mon..Fri 09:00'
systemd-analyze calendar --iterations=5 '*-*-* *:0/15'

It prints the normalized form and the next elapse times, and it exits nonzero on a syntax error, which makes it usable in CI.

Monotonic timers

Instead of a wall-clock date, a timer can fire relative to an event. These are what OnCalendar cannot express.

OptionStarts counting from
OnActiveSec=the timer unit being activated
OnBootSec=kernel boot
OnStartupSec=systemd itself starting
OnUnitActiveSec=the last time the service was activated
OnUnitInactiveSec=the last time the service deactivated

The classic cron replacement for "every 10 minutes, starting 2 minutes after boot":

[Timer]
OnBootSec=2min
OnUnitActiveSec=10min

That is a more honest "every 10 minutes" than */10 in cron, because the clock restarts when the job finishes rather than overlapping a slow run. Time values accept suffixes like us, ms, s, min, h, d, w, and a bare number is seconds.

Catching up, jitter, and accuracy

OptionDefaultWhat it does
Persistent=noStore the last run on disk and fire immediately at boot if the window was missed
AccuracySec=1minHow far systemd may delay firing to batch timers together
RandomizedDelaySec=0Add a random delay of 0 to this value on every fire
FixedRandomDelay=noMake that random delay stable per machine and unit
WakeSystem=noWake a suspended machine to run the job
RemainAfterElapse=yesKeep a one-shot timer loaded after it fires

Persistent=true is the anacron equivalent and is what you want on a laptop or any box that is not always on. Set AccuracySec=1s when a job genuinely needs to land on the second, because the 1 minute default means a 02:30 job may run any time in the following minute. RandomizedDelaySec is how you stop 400 machines from hitting the same endpoint at once, and FixedRandomDelay=yes keeps each machine's offset consistent across reboots.

Inspecting and troubleshooting

systemctl list-timers --all
systemctl status backup.timer
systemctl cat backup.timer backup.service
journalctl -u backup.service -n 50 --no-pager
sudo systemctl start backup.service    # run the job now, ignore the schedule
sudo systemctl restart backup.timer    # reload after editing the schedule
sudo systemd-analyze verify /etc/systemd/system/backup.timer

list-timers shows NEXT, LEFT, LAST, PASSED, UNIT, and ACTIVATES. An empty NEXT on a calendar timer usually means the unit is not enabled or the expression never elapses.

- After editing any unit file, run systemctl daemon-reload. Nothing picks up the change without it. - Start the service to test the command, and start the timer to test the schedule. Confusing the two is the most common mistake. - A failing job is not visible in list-timers. Add OnFailure=notify-failure@%n.service to the service, or scrape systemctl --failed. - Persistent=true writes a stamp under /var/lib/systemd/timers/. Deleting that file makes the next boot look like a missed window.

User timers

Every timer above works per user, with no root and no sudo, from ~/.config/systemd/user/.

systemctl --user daemon-reload
systemctl --user enable --now sync.timer
systemctl --user list-timers
sudo loginctl enable-linger "$USER"

Without enable-linger the user manager stops when the last session closes, so the timer does not run while you are logged out. That is the user-timer equivalent of a per-user crontab, which does keep running.

Timers versus cron

cronsystemd timer
Definitionone linetwo unit files
Missed runslost, or anacronPersistent=true
Outputmailedjournal, journalctl -u
Overlap preventionflockbuilt in, a service will not run twice
Resource limitsnoneMemoryMax, CPUQuota, Nice, cgroups
SandboxingnonePrivateTmp, ProtectSystem, User=
Randomized startRANDOM_DELAY in anacronRandomizedDelaySec
DependenciesnoneAfter=, Wants=, Requires=
Syntax checknone built insystemd-analyze calendar
PortabilityPOSIX, everywhereLinux with systemd

Reach for cron when the job is a one-liner on a box you do not control, and for a timer when you want logs, dependency ordering, resource caps, or catch-up behavior. See systemd.timer(5) and systemd.time(7).