Crontab ⇄ systemd Timer Converter
Paste a crontab line to get a matching .service and .timer unit with the correct OnCalendar= syntax, the commands to install it and its next run times. Or go the other way and turn an OnCalendar= expression back into a crontab schedule.
job.service
job.timer
Install & check
Next runs
Crontab
Line by line
Next runs
Why move cron jobs to systemd timers?
On any Linux distribution that boots with systemd you can replace a crontab entry with two small unit files: a .service that says what to run and a .timer that says when. It's more typing than one crontab line, but you get a lot in return:
- Logging for free. Everything the job prints goes to the journal.
journalctl -u backup.serviceshows each run with timestamps and the exit status. You no longer need>> /var/log/x.log 2>&1or rely onMAILTO. - Missed runs are caught up. With
Persistent=true, a job whose time passed while the machine was off or asleep runs once soon after the next boot. Plain cron silently skips it (anacron only covers daily, weekly and monthly jobs). - Visibility.
systemctl list-timerslists every timer with its last and next run time, andsystemctl status backup.serviceshows whether the last run failed. - No overlapping runs. A timer won't start the service again while the previous run is still active, so you don't need
flockwrappers. - Dependencies and resource control. The service can declare
After=network-online.target, run as a specificUser=, and use sandboxing and limits such asProtectSystem=,MemoryMax=,CPUQuota=orNice=. - Spread the load.
RandomizedDelaySec=adds a random delay to each run, so a fleet of servers doesn't hit the same backup target or API at exactly 00:00. - Test without waiting.
systemctl start backup.serviceruns the job right now in exactly the same environment the timer uses.
Cron is still simpler for a quick personal job, works on systems without systemd (Alpine with OpenRC, BSDs, many containers) and is understood by everyone. Use whichever fits.
OnCalendar= syntax
A calendar event has the form DayOfWeek Year-Month-Day Hour:Minute:Second. The weekday is optional, an omitted date means every day (*-*-*), and an omitted time means midnight. Seconds are optional and default to 00.
Mon..Fri *-*-* 09:30:00 | | | | | | `-- second | | | | | `-- minute | | | | `-- hour | | | `-- day of month (or ~N = Nth-last day) | | `-- month | `-- year `-- weekdays (optional): Mon Tue Wed Thu Fri Sat Sun
*matches every value.,separates a list:Sat,Sun,*-*-1,15...is a range:Mon..Fri,09..17:00. Note that it is two dots, not a dash, because the dash separates the date parts./repeats from a start value:*:00/15is every 15 minutes starting at :00,*-*-01/2is every other day starting on the 1st.~counts days from the end of the month:*-02~01is the last day of February,Mon *-05~07/1is the last Monday of May.- Shorthands:
minutely,hourly,daily,weekly(Monday 00:00),monthly,quarterly,semiannuallyandyearly. - A time zone can be appended:
*-*-* 09:00 Europe/BerlinorUTC. - A timer can have several
OnCalendar=lines; it fires whenever any of them matches.
Always check an expression before you deploy it. systemd-analyze calendar --iterations=5 'Mon..Fri 09:30' prints the normalized form and the next five times it matches.
Cron to OnCalendar examples
| crontab | OnCalendar= |
|---|---|
* * * * * | *-*-* *:*:00 (or minutely) |
*/5 * * * * | *-*-* *:00/5:00 |
0 * * * * | *-*-* *:00:00 (or hourly) |
30 2 * * * | *-*-* 02:30:00 |
0 9 * * 1-5 | Mon..Fri *-*-* 09:00:00 |
0 10 * * 6,0 | Sat,Sun *-*-* 10:00:00 |
0 0 1 * * | *-*-01 00:00:00 (or monthly) |
0 0 1 1,4,7,10 * | *-01,04,07,10-01 00:00:00 (or quarterly) |
0 0 * * 0 (@weekly) | Sun *-*-* 00:00:00. systemd's weekly means Monday. |
@reboot | No calendar event. Use OnBootSec=1min in the timer instead. |
Differences to watch out for
- Day-of-month and day-of-week. When both are restricted, cron runs if either matches:
0 0 13 * 5runs on every 13th and on every Friday. systemd requires both to match, soFri *-*-13means only Friday the 13th. This converter emits twoOnCalendar=lines to keep cron's behaviour. Going the other way, it adds adate +%utest to the crontab line. - Seconds. systemd timers can fire on any second; cron's resolution is one minute. Schedules such as
*:*:00/30have no cron equivalent. - Accuracy. By default systemd may delay a timer by up to a minute (
AccuracySec=1min) to batch wake-ups. SetAccuracySec=1sif you need second precision. - Environment. cron runs jobs through
/bin/shwith a minimalPATHand the user's home as working directory. A service runs the command directly, without a shell, from/. That's why commands with redirects, pipes or variables are wrapped in/bin/sh -c, and why$is written as$$and%as%%inside unit files. - The % sign. In a crontab, an unescaped
%ends the command and the rest is sent to standard input. Write\%for a literal percent, for example indate +\%F. - User timers. Units under
~/.config/systemd/useronly run while you are logged in, unless you enable lingering withloginctl enable-linger. - Missed runs. cron never catches up. A timer catches up only if
Persistent=trueis set, and only once, not once per missed slot.