Free Cron Expression Explainer
Paste a cron expression and understand exactly what it does.
Common Examples
How to Read Cron Expressions
A standard cron expression has 5 fields separated by spaces:
minute hour day month weekday
* · any value */n · every n units 1,5 · at 1 and 5 1-5 · range 1 through 5
Ranges: minute (0-59), hour (0-23), day (1-31), month (1-12), weekday (0-6, 0 = Sunday)
How to Use This Explainer
- Paste any 5-field cron expression. The tool also accepts
@daily,@hourly,@weekly,@monthly,@yearlyand the 6-field Quartz-style format with seconds. - Read the plain-English explanation. The full sentence appears immediately, with each individual field broken out so you can see exactly what each one is restricting.
- Verify against the next 10 scheduled runs. The list of upcoming executions is computed from your local time, sanity-check that the schedule actually matches what you intended before deploying.
- Use the Common Examples as templates. Click any example to load it; tweak the field you want to change.
Half a Century of One-Minute Ticks
Cron is the oldest scheduler still in continuous daily use on most of the world's servers. Its first appearance in the historical record is May 1975, when an early version shipped from AT&T Bell Laboratories as part of the Research Unix branch. The name is a nod to Chronos, the Greek personification of time, and the design has aged with strange grace: the same five whitespace-separated fields that Bell Labs engineers typed into /usr/lib/crontab in the mid-1970s still drive Kubernetes CronJobs, GitHub Actions schedules, and the nightly database backup on a virtual private server somewhere in Frankfurt right now. The 1975 implementation was minimal, there was one crontab, owned by root, serviced the whole machine. If a user wanted a recurring job, they asked the administrator to add a line by hand. Cron travelled into the wider world in Version 7 Unix, released in 1979. Robert Brown and Keith Williamson at Purdue University extended cron to handle multiple users in late 1979, introducing the per-user crontab -e workflow. The decisive rewrite came when Paul Vixie released vixie-cron 1.0 on 6 May 1987; vixie-cron formalised the special characters and introduced the @reboot, @hourly, @daily, @weekly, @monthly, @yearly shortcuts. Vixie 3.0 (27 December 1993) added step values (/) and shipped, with minor patches, into nearly every Linux distribution and BSD of the era. POSIX caught up in 1992 (IEEE Std 1003.2-1992). Every quirk of modern cron (the off-by-one Sunday numbering, the union-vs-intersection day-field bug) is a scar from this evolution; none of it was designed in one sitting.
Anatomy of a Five-Field Expression
A standard cron expression is five fields separated by whitespace. Read left to right, they ask: which minute, of which hour, of which day-of-month, of which month, of which day-of-week? The exact ranges are non-negotiable in POSIX cron: minute 0-59, hour 0-23, day-of-month 1-31, month 1-12, day-of-week 0-7 with both 0 and 7 representing Sunday. Each field accepts five operators that compose freely: * means every valid value; , separates a list of discrete values (0,15,30,45 * * * * runs at the top, quarter past, half past, and three-quarter past every hour); - is an inclusive range (9-17 in the hour field means 9, 10, 11, 12, 13, 14, 15, 16, 17); / is a step value (*/15 in the minute field means every fifteenth minute starting at zero, i.e. 0, 15, 30, 45). Months and days-of-week may also be written as three-letter abbreviations: JAN-DEC and SUN-SAT. A worked example: */15 9-17 * * 1-5 decodes as every fifteen minutes during the hours 9 through 17 inclusive, on every day of the month, every month of the year, Monday through Friday: "every quarter-hour during business hours on weekdays."
The Day-of-Month / Day-of-Week Trap
The most consequential cron quirk (the one that has caused outages, missed backups, and quietly wrong invoicing runs in production for thirty-five years) is the way cron combines the day-of-month and day-of-week fields when both are restricted. POSIX language is unusually precise here: "if both 'day of month' (field 3) and 'day of week' (field 5) are restricted (do not contain '*'), then one or both must match the current day." In other words, when neither field is wildcarded, cron takes the union of the two (the job runs on any day that matches either restriction. This is the opposite of what most users assume. Reading 0 0 13 * 5 aloud) "midnight on the 13th, on Fridays", naturally sounds like an intersection: only Friday the 13th. In vixie-cron and its descendants it actually means "every 13th of the month and every Friday," firing roughly nine times a month. Worse, vixie-cron decides whether to use union or intersection by inspecting only the first character of each day field. Paul Vixie himself acknowledged the behaviour as a bug but declined to change it, noting that fixing it would violate the principle of least astonishment, millions of crontabs were already written on the assumption that the existing behaviour was deliberate. The bug is therefore now a feature, immortal and propagating. The pragmatic mental model: if you find yourself restricting both day-of-month and day-of-week and you actually want intersection (e.g. "the second Tuesday of each month"), use the # operator in implementations that support it (Quartz, cronie with extensions): 0 12 ? * 2#2. In pure POSIX cron, intersection is genuinely impossible to express in a single expression and you must filter inside the script the cron job invokes.
The Shortcut Macros
@yearly/@annually=0 0 1 1 *: midnight, January 1@monthly=0 0 1 * *: midnight, first of each month@weekly=0 0 * * 0: midnight, Sunday@daily/@midnight=0 0 * * *: midnight every day@hourly=0 * * * *: top of every hour@reboot: special: fires once when the cron daemon starts (not once per system reboot, on systems where cron can be stopped and restarted,@rebootwill fire again)
These shortcuts are not POSIX. Strict POSIX-only environments will reject @daily; in practice every cron implementation any reader is likely to encounter (vixie-cron, cronie, fcron, ISC cron, container images) supports them.
The Dialects, Standard vs Quartz vs AWS vs K8s vs systemd
The "cron expression" is now a small family of mutually incompatible dialects. Standard 5-field cron (POSIX, vixie-cron, cronie): minute hour day-of-month month day-of-week, the universal lowest common denominator. Quartz Scheduler (6 or 7 fields, Java ecosystem): seconds minutes hours day-of-month month day-of-week [year]; introduces ?, L (last), W (weekday), # (nth weekday of month). Spring's @Scheduled and Spring Boot use Quartz. Kubernetes CronJob uses standard 5-field cron, with a separate spec.timeZone field that went GA in K8s 1.27 (the CronJob resource itself was GA in 1.21, April 2021); timezone format is the IANA tz database (Europe/Berlin, America/New_York). GitHub Actions cron schedules use 5-field POSIX cron and run in UTC. AWS EventBridge cron uses 6 fields (minutes hours day-of-month month day-of-week year), requires the ? operator on either day-of-month or day-of-week (you can't restrict both), and uses 1-7 with SUN=1, meaning an EventBridge expression 0 12 ? * 2 * runs at noon on Monday, not Tuesday. systemd timers use a different syntax altogether (OnCalendar=*-*-* 02:00:00) and are gradually replacing cron on modern Linux for system-level scheduling, though both ship side by side on every major distro. Cloud Scheduler (Google Cloud) uses standard 5-field cron with explicit timezone configuration. Translating expressions between platforms requires careful attention: a copy-pasted EventBridge schedule will run on the wrong weekday in vixie-cron because of the day-of-week numbering shift.
Common Patterns Worth Memorising
*/15 * * * *: every 15 minutes (00:00, 00:15, 00:30, 00:45 of every hour)*/15 9-17 * * 1-5: every 15 minutes during business hours, weekdays only0 9 * * 1-5: every weekday at 9 AM0 6,18 * * *: twice a day, at 6 and 18 (using a list, not a range)0 0 1 * *: midnight on the 1st of each month0 0 L * *: midnight on the last day of each month (Lis Quartz/cronie extension)0 3 1-7 * 1: first Monday of each month at 3 AM (uses the union-bug deliberately: matches days 1-7 AND Mondays, but the only days that satisfy both restrictions in any given month are the first Monday)0 0 1 1,4,7,10 *: midnight on the first day of each calendar quarter0 0 * * 0,6: midnight on weekends
Common Gotchas
Timezone. cron uses the system's local time by default, surprising on cloud machines that default to UTC. A 9 AM cron job on a UTC server fires at 4 AM Eastern. Modern schedulers (Kubernetes 1.27+, AWS EventBridge, Cloud Scheduler) added explicit timezone fields; classic cron does not. The "*/N starts at 0" rule. */15 is 0, 15, 30, 45, not 5, 20, 35, 50. To start at a non-zero offset you have to enumerate (5,20,35,50) or use the Quartz-only 5/15 syntax. The 60-minute pile-up trap. A job that takes longer than its schedule interval can pile up, three 30-minute backups firing every 15 minutes will overlap. The standard mitigation is flock(1): * * * * * /usr/bin/flock -n /tmp/myjob.lock /path/to/myjob ensures only one instance runs at a time; the -n flag makes lock acquisition non-blocking, so subsequent invocations exit silently rather than queueing. DST anomalies. A cron job scheduled for 02:30 will fire twice on the day clocks go back and not at all on the day they go forward. cron has no concept of "wall-clock time accounting for DST"; if your schedule must skip DST transitions, anchor it to a non-affected hour (3 AM or later in most time zones) or use a scheduler that understands timezone semantics. PATH stripping. cron jobs run with a minimal PATH (/usr/bin:/bin) and a near-empty environment; scripts that work in your interactive shell may fail in cron because node, python3 or aws are not on the inherited PATH. Either set PATH= at the top of the crontab or use absolute paths in the cron command. Email overflow. By default cron emails the output of every job to the user's local mailbox; on a server with no mail configured this silently fills /var/spool/mail until the disk runs out. Either redirect output (>/dev/null 2>&1), set MAILTO="" at the top of the crontab, or actually configure a mail forwarder.
Modern Alternatives, When to Reach for Something Else
cron is excellent for simple recurring tasks on a single machine. It is poor at: distributed scheduling (the same job triggered once across a fleet rather than once per machine), event-driven triggers (run when a queue receives a message, not on a clock), monitoring (cron silently fails if the job exits non-zero unless you set up email forwarding), retries (no built-in mechanism, failed jobs run again next cycle and accumulate state), and dependencies (run job B only after job A completed successfully). For those cases the modern answer is one of: Kubernetes CronJob (cluster-aware scheduling with retry and parallelism policies), AWS EventBridge + Lambda or Step Functions (event-driven with built-in observability), Apache Airflow or Prefect (DAG-based workflow orchestration with explicit dependencies), Temporal (durable workflow execution), healthchecks.io (a "dead man's switch" that pings you when a cron job doesn't run on schedule). For one-machine recurring jobs in 2026, plain cron is still the right answer; for anything else, one of the alternatives is worth the extra setup.
Frequently Asked Questions
What does * mean in a cron expression?
An asterisk (*) in a cron field means "every valid value", every minute, every hour, every day, every month, every day-of-week. * * * * * runs every minute of every day. The asterisk is also special in the day-of-month and day-of-week fields because of the union-vs-intersection trap: when either day field has a leading *, vixie-cron uses intersection mode; when neither does, it uses union mode and runs on the union of both restrictions.
How do I run a cron job every 15 minutes?
Use the step notation: */15 * * * * runs every 15 minutes, at xx:00, xx:15, xx:30 and xx:45. Note that */N always starts at 0; you cannot use */15 to mean "every 15 minutes starting at minute 5", for that you'd write 5,20,35,50 * * * *. Quartz-dialect cron supports 5/15 as a non-standard alternative.
What's the difference between cron and at?
cron runs jobs on a repeating schedule (every minute, daily, weekly). The at command schedules a one-time job to run at a specific future time, at 14:30 tomorrow queues a job for that single moment. Use cron for recurring tasks and at for one-off future executions. Both descend from the same Bell Labs / vixie-cron lineage and were eventually folded into the same daemon on most systems.
Why does my cron job not match what I expected?
Five most common causes, in rough order of frequency: (1) Day-of-month vs day-of-week confusion (cron uses the union of both when both are restricted, not the intersection. (2) Timezone) cron uses the server's local time by default, often UTC on cloud machines. (3) PATH issues, your interactive shell's PATH isn't inherited, so commands that work at the prompt may fail in cron. (4) The */N always-starts-at-0 trap. (5) Output not being captured anywhere, failed jobs silently disappear if you haven't set up email or redirected output to a log file. This explainer's "next 10 scheduled runs" panel is the cheapest way to verify what your expression actually means before deploying.
Does this support non-standard cron formats?
It handles standard 5-field POSIX/vixie-cron, the 6-field Quartz/Spring variant with seconds, and the special strings @hourly, @daily, @weekly, @monthly, @yearly and @reboot. It does not handle full Quartz extensions (L, W, #) or AWS EventBridge's 1-based weekday numbering, for those, use the platform's own validator before deploying.
Is my cron expression sent anywhere?
No. Parsing and explanation run entirely in your browser via JavaScript. Pasted expressions never cross the network, verify in DevTools' Network tab while you click Explain. Safe for cron expressions in production CI configs, infrastructure code, and operational runbooks where the schedule itself might be sensitive (e.g. nightly database export schedules that hint at downtime windows).