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 /.
| Expression | Meaning |
|---|---|
*-*-* 02:30:00 | every day at 02:30 |
Mon..Fri 09:00 | weekdays at 09:00 |
Mon,Thu *-*-* 18:00 | Mondays and Thursdays at 18:00 |
*-*-01 04:00 | first of every month at 04:00 |
*-*-~01 23:00 | last day of every month at 23:00 |
*-*-* *:0/15 | every 15 minutes |
*-*-* 09..17:00 | on the hour from 09:00 to 17:00 |
2027-01-01 00:00:00 | once, at that instant |
*-*-* *:*:00 UTC | every minute, in UTC |
Shorthands expand to the same thing:
| Shorthand | Expands to |
|---|---|
minutely | *-*-* *:*:00 |
hourly | *-*-* *:00:00 |
daily | *-*-* 00:00:00 |
weekly | Mon *-*-* 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.
| Option | Starts 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
| Option | Default | What it does |
|---|---|---|
Persistent= | no | Store the last run on disk and fire immediately at boot if the window was missed |
AccuracySec= | 1min | How far systemd may delay firing to batch timers together |
RandomizedDelaySec= | 0 | Add a random delay of 0 to this value on every fire |
FixedRandomDelay= | no | Make that random delay stable per machine and unit |
WakeSystem= | no | Wake a suspended machine to run the job |
RemainAfterElapse= | yes | Keep 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
| cron | systemd timer | |
|---|---|---|
| Definition | one line | two unit files |
| Missed runs | lost, or anacron | Persistent=true |
| Output | mailed | journal, journalctl -u |
| Overlap prevention | flock | built in, a service will not run twice |
| Resource limits | none | MemoryMax, CPUQuota, Nice, cgroups |
| Sandboxing | none | PrivateTmp, ProtectSystem, User= |
| Randomized start | RANDOM_DELAY in anacron | RandomizedDelaySec |
| Dependencies | none | After=, Wants=, Requires= |
| Syntax check | none built in | systemd-analyze calendar |
| Portability | POSIX, everywhere | Linux 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).