Developer tools

How to read and write a cron expression (with examples you can copy)

What each of the five fields of a cron expression means, what *, comma, hyphen and slash do, the mistakes that confuse most people and ready-made examples to copy.

A cron expression is a line of five space-separated fields describing when a task should run: minute, hour, day of month, month and day of week. 0 9 * * 1-5 means “at 9:00 from Monday to Friday”. The same syntax is used by crontab on Linux and macOS, by GitHub Actions, by Kubernetes and by most schedulers, with small differences worth knowing.

This article explains each field, the four special characters, the cases that confuse most people (Sunday as 0 or 7, how day of month and day of week combine, time zones) and collects examples you can copy.

The five fields

┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12 or jan-dec)
│ │ │ │ ┌───────────── day of week (0-6, Sunday = 0; often also 7 = Sunday, and mon-sun)
│ │ │ │ │
* * * * *  command

The order never changes. Abbreviated English names (jan, mon) are accepted by most implementations, but numbers work everywhere, so they are the safe choice.

The four special characters

Character Meaning Example
* Any value * * * * * every minute
, List of values 0 9,18 * * * at 9:00 and at 18:00
- Range 0 9 * * 1-5 at 9:00 Monday to Friday
/ Step */15 * * * * every 15 minutes (0, 15, 30, 45)

They combine: 0 8-20/2 * * 1-5 runs every two hours between 8:00 and 20:00 on weekdays. One detail about steps: */15 in the minute field starts at 0; 5/15 (valid in some implementations, not in POSIX) would start at minute 5.

Examples ready to copy

Expression When it runs
*/5 * * * * Every 5 minutes
0 * * * * Every hour, on the hour
30 2 * * * Every day at 2:30
0 9 * * 1-5 Monday to Friday at 9:00
0 10 * * 6,0 Saturdays and Sundays at 10:00
0 0 1 * * The 1st of every month at midnight
0 0 1 1 * 1 January at midnight
0 6 * * 1 Mondays at 6:00
0 */6 * * * Every 6 hours (0:00, 6:00, 12:00, 18:00)
15 14 1 * * The 1st of every month at 14:15

If you want to check an expression before putting it into production, the cron expression tool on AIMRAN Tools translates it into plain language and lists the next runs, which avoids the classic surprise of discovering the mistake once the job has already failed to run.

The three points that confuse most people

1. Day of month and day of week combine with “or”

According to crontab(5), if both fields have a value other than *, the job runs when either matches. 0 9 13 * 5 does not mean “Friday the 13th” but “every 13th at 9:00 and also every Friday at 9:00”. For “Friday the 13th” you have to check the weekday inside the command itself.

2. Sunday is 0 (and usually 7 too)

The POSIX standard defines Sunday as 0. Vixie cron (the usual one on Linux) also accepts 7. Other schedulers do not, so use 0 if you want portability.

3. The time zone is not in the expression

A cron expression carries no time zone: it is interpreted in the one of the system running it. In GitHub Actions it is always UTC, so 0 9 * * 1-5 runs at 9:00 UTC (11:00 in Spain in summer). In Kubernetes you can add timeZone: "Europe/Madrid" to the CronJob spec. On a Linux server it depends on the system configuration or on the CRON_TZ variable if the cron supports it.

Differences between environments

Environment Particularities
Linux / macOS (crontab -e) Five fields. Vixie cron accepts shortcuts such as @daily, @hourly, @weekly, @monthly, @reboot. Output is sent by local mail unless redirected.
GitHub Actions (on: schedule) Five fields in quotes, UTC, minimum interval of 5 minutes. Runs can be delayed under heavy load and the scheduler is disabled in repositories with no activity for 60 days.
Kubernetes (CronJob) Five fields, supports timeZone, @hourly-style shortcuts and concurrency policies (concurrencyPolicy).
Some tools (Quartz, Spring, certain cloud services) Add a sixth seconds field at the start or a seventh for the year. Pasting a five-field expression there shifts it and completely changes its meaning.

Good practices

  1. Check the expression with a tool that lists the next runs before saving it.
  2. Document the intent in a comment above it (# daily backup 2:30), because the expression alone is not readable at a glance.
  3. Avoid exact hours when you do not need them: many jobs pile up at 0 0 * * * and 0 * * * *; shifting a few minutes spreads the load (cloud schedulers do exactly that).
  4. Redirect the output (>> /var/log/job.log 2>&1) so cron does not pile up mail and you can debug.
  5. Make the job idempotent: if cron starts it twice or it overlaps the previous run, nothing should break. A lock (flock -n) prevents overlaps.

Conclusion

Five fields, four symbols and three traps: the “or” between day of month and day of week, Sunday as 0 and the system time zone. That covers almost any schedule. And before you trust an expression, verify it: two minutes with a checker save a job that silently did not run at 2:30 in the morning.

Sources and references

  1. crontab(5): tables for driving cron (man7.org) man7.org
  2. GitHub Docs: Events that trigger workflows, schedule docs.github.com
  3. Kubernetes: CronJob kubernetes.io
  4. POSIX crontab specification (The Open Group) pubs.opengroup.org

Related tools

Herramientas gratuitas de AIMRAN Tools que funcionan en tu navegador, sin registro.

Ver todas las herramientas
  • Cron expressions

    Explains a cron expression in plain language and shows the next runs.