Cron tools › Convert
Cron to systemd timer
Paste a crontab schedule and get the OnCalendar= line, a .timer and a
.service unit, and the commands to enable them. The OnCalendar expression is expanded by the same evaluator as the
OnCalendar tester and compared with the cron line's next 500 runs.
OnCalendar
Unit files
Try one
How to replace a cron job with a systemd timer
- Paste the schedule part of the crontab line and fill in a unit name and the command.
- Save the two units as
/etc/systemd/system/name.timerandname.service— the Download buttons give you the files. - Run
sudo systemctl daemon-reload, thensudo systemctl enable --now name.timer. - Confirm the schedule with
systemd-analyze calendar '…'andsystemctl list-timers. - Remove the old line from the crontab, or the job will run twice.
How cron fields map to OnCalendar
| Cron | OnCalendar | Note |
|---|---|---|
| 30 9 * * * | *-*-* 09:30:00 | date as Y-M-D, time as H:M:S |
| 0 9 * * 1-5 | Mon..Fri *-*-* 09:00:00 | weekday names, ranges with .. |
| */15 * * * * | *-*-* *:00/15:00 | repetition /n from a start value |
| 0 0 1,15 * * | *-*-01,15 00:00:00 | lists with , |
| 0 0 1 */3 * | *-01/3-01 00:00:00 | every third month from January |
| 0 0 L * ? (Quartz) | *-*~01 00:00:00 | ~ counts from the end of the month |
| 0 0 1 * 1 | two OnCalendar= lines | cron ORs the day fields; systemd ANDs weekday and date |
| @daily | *-*-* 00:00:00 | systemd's own daily means the same |
Quartz, Spring and AWS input works too: seconds carry over (systemd has a seconds field), an nth weekday such as
2#1 becomes Mon *-*-01..07, and a last weekday such as 6L becomes Fri *-*~07/1.
"Nearest weekday" (W, LW) has no OnCalendar form and is refused with an explanation.
The timer settings that matter
- Persistent= — off by default. With
true, a run that was missed while the timer was inactive (the machine was off) is caught up when it becomes active again. - AccuracySec= — defaults to 1min, so systemd may start the job up to a minute after the calendar time to
group wake-ups. Choose
1sif the exact second matters. - RandomizedDelaySec= — defaults to 0. A value such as
5minspreads the start over a random delay, which helps when many machines share the same schedule. - Several OnCalendar= lines — allowed; the timer fires whenever any of them elapses. That is how cron's day-of-month OR day-of-week is expressed.
FAQ
Why did I get two OnCalendar= lines?
Because the cron line sets both day fields. Cron then runs when either matches, while the weekday and date in one OnCalendar expression must both match. One line per half, in the same timer, gives the cron behaviour back.
Does the timer use my local time or UTC?
Without a zone in the expression, the machine's local time — the same as cron. Pick a zone in the tool and it is
appended to each OnCalendar= line (for example Mon..Fri *-*-* 09:00:00 Europe/Berlin).
Why is my command wrapped in /bin/sh -c?
ExecStart= runs a program directly, not through a shell, so pipes, redirects, && and variables would be
passed as literal arguments. The converter wraps such commands in /bin/sh -c "…" and doubles % and
$, which unit files treat as specifiers.
How do I see when the timer will fire?
systemctl list-timers shows the next elapse of every active timer, and
systemd-analyze calendar 'Mon..Fri *-*-* 09:00:00' prints the normalized form and the next elapse of an
expression. The OnCalendar tester does the same in the browser.