Skip to content
CronChief

Cron syntax across Linux, Kubernetes, GitHub Actions, AWS and Quartz

The five-field expression you know shows up all over modern infrastructure — with subtle, breaking differences. A field-by-field comparison across the systems you'll actually meet.

The five-field cron expression escaped Unix decades ago. Today you'll meet it in container orchestrators, CI pipelines and cloud schedulers — each with its own subtle twist. Copying an expression from one to another without checking is a classic way to get a job that runs at the wrong time, or not at all.

Standard Unix cron

The baseline. Five fields — minute, hour, day-of-month, month, day-of-week — plus the @daily-style macros. Runs in the server's local timezone. Everything below is a variation on this.

0 9 * * 1-5   →  9 AM on weekdays

Kubernetes CronJob

Kubernetes uses standard 5-field cron verbatim, so your expressions carry over directly. The differences are around it, not in it:

  • spec.schedule holds the cron string.
  • spec.timeZone (stable since Kubernetes 1.27) lets you pin a timezone instead of relying on the controller's — a welcome fix for the timezone trap.
  • concurrencyPolicy (Allow / Forbid / Replace) controls what happens when a run is still going and the next is due — the built-in answer to overlapping jobs.
  • startingDeadlineSeconds decides whether a missed run (e.g. the controller was down) still fires.

So the expression is standard; the behaviour around missed and overlapping runs is configurable in a way plain cron never was.

GitHub Actions

GitHub Actions schedules workflows with 5-field cron, but with hard constraints:

on:
  schedule:
    - cron: "0 9 * * 1-5"
  • UTC only. There is no timezone option — 0 9 * * * is 9 AM UTC, full stop. Do the offset in your head.
  • The shortest interval is 5 minutes; anything more frequent is rejected or coalesced.
  • Scheduled runs are best-effort and can be delayed during high load (sometimes by many minutes), so don't rely on exact timing.
  • Macros like @daily are not supported — use the five fields.

AWS EventBridge / CloudWatch

AWS Scheduler and EventBridge use a 6-field cron() expression — the extra field is year — and they change two symbols:

cron(0 9 ? * MON-FRI *)   →  9 AM weekdays
  • Six fields: minute, hour, day-of-month, month, day-of-week, year.
  • You must use ? in either day-of-month or day-of-week (you can't specify both as *), because AWS forbids the ambiguous combination.
  • Day-of-week is 1–7 with Sunday = 1, not 0 — off by one from Unix.
  • There's also a simpler rate(5 minutes) form for fixed intervals.

This is the dialect most likely to trip you up: an expression copied from a Linux crontab will usually be rejected outright.

Quartz (Java) and Spring @Scheduled

Quartz — and Spring's @Scheduled(cron = "…"), which uses Quartz-style syntax — expects 6 or 7 fields, leading with seconds:

0 0 9 ? * MON-FRI   →  9 AM weekdays
  • Fields: seconds, minutes, hours, day-of-month, month, day-of-week, and an optional year.
  • Like AWS, it uses ? for "no specific value" in the day fields.
  • It adds special characters plain cron lacks: L (last), W (nearest weekday) and # (nth weekday of the month) — so "the first Monday of the month" (? * 2#1) is finally expressible.
  • Day-of-week is 1–7 with Sunday = 1.

The quick comparison

System Fields Seconds? Timezone Day-of-week
Unix cron 5 No Server local 0–6, Sun = 0
Kubernetes 5 No Configurable 0–6, Sun = 0
GitHub Actions 5 No UTC only 0–6, Sun = 0
AWS EventBridge 6 (+year) No UTC / configurable 1–7, Sun = 1
Quartz / Spring 6–7 Yes Configurable 1–7, Sun = 1

The takeaway

"Cron syntax" is really a family of closely-related dialects. Before you move an expression between systems, check three things: how many fields it expects, whether Sunday is 0 or 1, and which timezone it runs in. When in doubt, decode the plain 5-field version in the parser first, then translate it deliberately into the target system's dialect.