Cron#

A cron expression is a five- or six-field DSL describing a recurring schedule. Originally the input syntax for the Unix cron daemon (1975); now used by Kubernetes CronJob, GitHub Actions, AWS EventBridge, GitLab CI, systemd timers (via translation), Quartz, and basically every scheduler that needs to express “run this on a schedule”.

The Five Fields#

A standard Unix cron expression has five fields.

┌───────── minute       (0-59)
│ ┌─────── hour         (0-23)
│ │ ┌───── day of month (1-31)
│ │ │ ┌─── month        (1-12)
│ │ │ │ ┌─ day of week  (0-7, where 0 and 7 are Sunday)
│ │ │ │ │
* * * * *  command-to-run

Field Syntax#

Each field accepts more than a single value. Wildcards, explicit lists, ranges, and steps all compose, enough to express most schedules without leaving cron syntax. Aliases for days of the week and months exist; the table below is the operator’s quick reference.

Form

Meaning

*

every value in the field

5

exactly 5

5,15,25

list of values

5-10

range

*/5

step (every 5)

0-30/5

step within a range

Day-of-week aliases: SUN MON TUESAT. Month aliases: JAN FEBDEC.

Common Recipes#

The cron expressions an operator writes most weeks. Daily, hourly, every-N-minutes, weekday-only, monthly, and yearly all show up regularly, worth recognizing on sight from a crontab review or a CI workflow file.

Expression

Schedule

* * * * *

every minute

0 * * * *

top of every hour

*/15 * * * *

every 15 minutes

0 0 * * *

daily at midnight

0 9 * * 1-5

weekdays at 9 AM

30 8 * * 1

Mondays at 8:30 AM

0 0 1 * *

first day of every month

0 0 1 1 *

once a year, midnight Jan 1

0 0 * * 0

Sundays at midnight

15 14 1 * *

14:15 on the 1st of every month

Special Strings#

Many cron implementations accept descriptive aliases.

Alias

Equivalent

@yearly / @annually

0 0 1 1 *

@monthly

0 0 1 * *

@weekly

0 0 * * 0

@daily / @midnight

0 0 * * *

@hourly

0 * * * *

@reboot

at startup (Unix cron only)

Six-Field Variant (Quartz)#

Quartz Scheduler (Java), GitLab pipelines, and a few others add a seconds field at the start.

second minute hour day-of-month month day-of-week [year]
─────────────────────────────────────────────────────────
0      0      9    *            *     MON-FRI      *

Don’t paste a Unix cron into a Quartz field; the meanings of position 1 differ.

Day-of-Month / Day-of-Week Quirk#

When both day-of-month and day-of-week are restricted (i.e. neither is *), Unix cron treats them as OR:

0 0 13 * 5      runs on the 13th OR any Friday  (Unix)

Quartz uses ? in one of the two fields to mean “ignore this side”:

0 0 0 13 * ?    runs on the 13th
0 0 0 ? * FRI   runs on Fridays

crontab#

The Unix cron daemon reads schedules from per-user crontab files.

$ crontab -e
$ crontab -l
$ crontab -r

Each line: schedule + command, optionally with MAILTO= / PATH= etc. at the top. % characters become newlines in the command unless escaped (a famous source of bugs).

Cron in Modern Schedulers#

The Unix daemon’s syntax has propagated into nearly every scheduler in the modern stack. Kubernetes, GitHub Actions, GitLab, AWS EventBridge, and systemd all accept some flavor of cron expression, with subtle differences in field count, time zone, and quirks worth knowing.

  • Kubernetes CronJob, spec.schedule accepts a five-field expression.

  • GitHub Actions, on.schedule.cron is a five-field expression (UTC).

  • GitLab CI, schedules use a six-field Quartz-style expression.

  • AWS EventBridge, six-field cron(...) expressions; also supports rate(n unit).

  • systemd timers, different syntax (OnCalendar=Mon..Fri 09:00); systemd-analyze calendar translates and previews next firings.

Tooling#

The aids that turn cron expressions from write-only into auditable. crontab.guru explains any expression in English; cronitor and healthchecks.io watch for missed runs; parser libraries compute next-firing previews. Reading a schedule before deploying it is the cheapest mistake to avoid.

  • crontab.guru, the explainer for any expression. Type and read the English version back.

  • cronitor / healthchecks.io, monitoring missed runs.

  • cron-validator libraries in most languages.

  • croniter (Python), cronstrue (JS), cron-parser (JS) for computing next firings.

Pitfalls#

The traps that produce incident reports. Time zone confusion, unmade-up missed runs, overlapping concurrent runs, DST double-fires, % getting interpreted as newlines, and cryptic expressions with no comment all surface in production sooner or later. Each has a clean mitigation.

  • Time zone, Unix cron uses the system time zone; CI / cloud schedulers are usually UTC. Set CRON_TZ= in the crontab or be explicit in the docs.

  • Missed runs, if the host is asleep / off, runs are not made up unless the scheduler does so explicitly (systemd’s Persistent=true for OnCalendar, anacron for cron).

  • Concurrent runs, if a run takes longer than the interval, the next one starts anyway. Use a lock file or scheduler concurrency control.

  • DST, daily runs around 02:00 may run twice or skip a day in fall / spring. Use UTC schedules or test around DST boundaries.

  • ``%`` characters, treated as newlines in commands; escape with \%.

  • Long expressions are illegible, use @daily / @hourly where possible, or comment.

When (Not) to Use Cron#

The kinds of work cron is good at, and the kinds it isn’t. Periodic jobs with fixed timing are the sweet spot. Job dependencies, retry-on-failure semantics, ad-hoc retries, and sub-minute precision all leave cron’s domain and want a real scheduler, queue, or workflow engine.

  • Use for periodic jobs whose timing is fixed: daily reports, hourly cleanup, weekly reindexing.

  • Don’t use for orchestration / dependencies between jobs, use Airflow, Prefect, Dagster, Argo Workflows.

  • Don’t use for ad-hoc retry / queue work, use a queue (Sidekiq, Celery, BullMQ).

  • Don’t use for sub-minute scheduling, you’ve left cron’s domain; use a real scheduler or run a long-lived loop.