Translate classic 5-field Linux crontab schedules into modern systemd.timer and systemd.service unit files—or convert OnCalendar= expressions back to cron syntax. Preview upcoming execution timestamps in local time or UTC and generate ready-to-deploy installation commands.
Migrating Scheduled Jobs from Cron to Systemd Timers
On modern Linux distributions (Debian, Ubuntu, RHEL, Rocky Linux, Arch), systemd timers offer significant operational advantages over traditional /etc/crontab and user crontabs. Because every timer triggers a corresponding .service unit, scheduled tasks gain native integration with journald structured logging, cgroup resource limits (CPU/Memory quotas), security sandboxing, and dependency management.
1. Crontab vs. Systemd OnCalendar Syntax
A standard crontab entry consists of five time fields followed by the command: Minute (0-59), Hour (0-23), Day of Month (1-31), Month (1-12), and Day of Week (0-7). Systemd replaces this positional format with explicit OnCalendar= specifications defined in systemd.time(7):
- Format:
DayOfWeek Year-Month-Day Hour:Minute:Second - Cron
30 2 * * *(Daily at 02:30) converts toOnCalendar=*-*-* 02:30:00 - Cron
*/15 * * * *(Every 15 minutes) converts toOnCalendar=*-*-* *:00/15:00 - Cron
0 9 * * 1-5(Weekdays at 09:00) converts toOnCalendar=Mon..Fri *-*-* 09:00:00
2. Critical Differences: PATH, Coalescing, and Day-of-Week Logic
When migrating production scripts from cron to systemd, administrators must account for three behavioral differences:
- Boolean OR vs. AND on Day Restrictions: In Vixie cron, if both Day-of-Month and Day-of-Week are restricted (not
*), cron executes the job when either condition matches (logical OR). In contrast, a single systemdOnCalendar=line requires both the weekday and the calendar date to match (logical AND). This converter automatically detects dual restrictions and outputs two separateOnCalendar=directives in the timer unit to preserve exact cron behavior. - Timer Coalescing (
AccuracySec=): By default, systemd groups timer events within a 1-minute window (AccuracySec=1min) to save power and reduce CPU wakeups. If your job requires second-level precision, explicitly setAccuracySec=1s. - Missed Runs (
Persistent=true): Similar toanacron, addingPersistent=truestores the last trigger timestamp on disk. If the server was powered off during a scheduled window, systemd immediately executes the job upon boot.
Frequently Asked Questions
Why does systemd wrap my command in /bin/sh -c?
/bin/sh), systemd's ExecStart= directive executes binaries directly via execve() without a shell. If your command uses pipes (|), output redirection (>), environment variable expansion ($VAR), or relative paths, it must be wrapped in /bin/sh -c '...'.How do I debug a systemd timer that failed to run?
systemctl list-timers your-unit.timer. Next, inspect the exit code and stdout/stderr logs of the associated service using journalctl -u your-unit.service -n 50 --no-pager. You can also test your OnCalendar= syntax directly with systemd-analyze calendar "expression".How are percent signs (%) handled differently in crontab and systemd?
% characters are converted into newlines and everything after the first % is passed as standard input to the command (requiring \% for commands like date +\%F). In systemd unit files, % introduces unit specifiers (such as %i or %H), so literal percent signs must be doubled as %%. This tool automatically escapes them for you.What is the systemd equivalent of @reboot in crontab?
@reboot is not a calendar event, so it does not use OnCalendar=. Instead, use OnBootSec=1min (or OnStartupSec=) inside the [Timer] section, or simply enable the .service unit directly with WantedBy=multi-user.target.Why doesn't my systemd timer fire at the exact second?
AccuracySec=1min to batch background wakeups and optimize power consumption. Setting AccuracySec=1s in your .timer file forces systemd to trigger the service within a one-second window of the scheduled timestamp.