SecOps Toolkit

Crontab to Systemd Timer Converter & Validator

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 to OnCalendar=*-*-* 02:30:00
  • Cron */15 * * * * (Every 15 minutes) converts to OnCalendar=*-*-* *:00/15:00
  • Cron 0 9 * * 1-5 (Weekdays at 09:00) converts to OnCalendar=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 systemd OnCalendar= line requires both the weekday and the calendar date to match (logical AND). This converter automatically detects dual restrictions and outputs two separate OnCalendar= 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 set AccuracySec=1s.
  • Missed Runs (Persistent=true): Similar to anacron, adding Persistent=true stores 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?
Unlike cron, which executes every line inside a default POSIX shell (/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?
First, check the timer schedule and last trigger time using 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?
In a crontab file, unescaped % 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?
By default, systemd applies 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.