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 |
|
exactly 5 |
|
list of values |
|
range |
|
step (every 5) |
|
step within a range |
Day-of-week aliases: SUN MON TUE … SAT.
Month aliases: JAN FEB … DEC.
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 |
|
top of every hour |
|
every 15 minutes |
|
daily at midnight |
|
weekdays at 9 AM |
|
Mondays at 8:30 AM |
|
first day of every month |
|
once a year, midnight Jan 1 |
|
Sundays at midnight |
|
14:15 on the 1st of every month |
Special Strings#
Many cron implementations accept descriptive aliases.
Alias |
Equivalent |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
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.scheduleaccepts a five-field expression.GitHub Actions,
on.schedule.cronis a five-field expression (UTC).GitLab CI,
schedulesuse a six-field Quartz-style expression.AWS EventBridge, six-field
cron(...)expressions; also supportsrate(n unit).systemd timers, different syntax (
OnCalendar=Mon..Fri 09:00);systemd-analyze calendartranslates 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-validatorlibraries 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=truefor 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/@hourlywhere 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.