flashman
← All guides

Translate cron schedules into systemd OnCalendar

Translate five-field cron into systemd OnCalendar with weekday names, AND semantics, time zones, persistence, accuracy, and a preview of the next runs.

2026-09-30 · 8 min read

  • cron
  • systemd
  • scheduling

systemd timers replace a cron daemon on many Linux hosts. The calendar language in OnCalendar is documented in systemd.time, and it does not accept a five-field cron expression as its grammar.

A correct migration writes the human schedule first, encodes it as a calendar event, and checks the next runs on the machine that will execute the unit. Copying fields across in order is how Monday jobs start running on the first of the month.

Map the fields into a calendar event

A daily cron such as 0 2 * * * becomes *-*-* 02:00:00. A Monday cron such as 0 9 * * 1 becomes Mon *-*-* 09:00:00. Use English weekday names. systemd numbers and abbreviations are easy to misread next to cron's numeric Sunday.

  • Put the clock time on the right, including seconds.
  • Use *-*-* when every date is acceptable.
  • Use Mon..Fri for a weekday range.
  • Replace */15 with an OnCalendar repetition such as *:0/15.

Respect AND semantics and special aliases

If the expression sets both a date and a weekday, systemd runs only when both match. A cron implementation that ORs those two fields will run on more days. Write the broader schedule as two timer units, or accept the intersection on purpose.

Aliases such as hourly, daily, and weekly are real calendar events. hourly is the top of every hour, not 'about once an hour'. Read the alias in the systemd.time manual for your version before using it in a unit.

Set the clock, the zone, and missed runs

Calendar events use the system local time unless the expression includes a zone. Set the container or host TZ explicitly. A unit tested on a laptop and deployed to a UTC image will shift by the laptop's offset.

Persistent=true starts a timer event that elapsed while the machine was off. Without it, that slot is skipped. AccuracySec= can delay the start by its default of about a minute, and RandomizedDelaySec= spreads load. Include those delays in the runbook so an on-call engineer does not treat a late start as a missed schedule.

Keep cron and systemd side by side during migration

Leave the old cron disabled only after the timer has fired on a real calendar boundary you care about: a Monday, a month boundary, and a daylight-saving change. Diff the unit file in review, and store the sentence form of the schedule in the unit comment.

Do not run both schedulers on the same job unless the job is idempotent and overlap is intended. Two triggers are a duplicate-run incident waiting for the first boot.

Preview the instants you will actually get

Use Flashman's cron helper to confirm the old five-field meaning, the timestamp converter for zones and offsets, diff for the unit, the Markdown editor for the runbook sentence, and the YAML/JSON converter when the timer is rendered from a template.

Compare systemd-analyze calendar output with the sentence. Test a missed boot, a daylight-saving Sunday, a weekday range, and a date-plus-weekday intersection before enabling the timer.

Try these tools