What is a Cron Expression?
Cron expressions explained: the five fields, special characters, and common scheduling patterns.
A cron expression is a compact string that tells a scheduler when to run a job. It originated with the Unix cron daemon in the 1970s and remains the standard way to schedule recurring tasks — backups, report generation, cache cleanup — on servers, CI systems, and cloud schedulers.
The classic form is five space-separated fields: minute, hour, day of month, month, and day of week. Each field narrows down the schedule, and when all five match the current time, the job runs. Special characters within the fields let you express patterns like 'every 15 minutes' or 'weekdays at 9am' without writing out every value.
What is a cron expression?
A cron expression is a schedule written as text. The cron daemon — or a library like node-cron, Quartz, or the GitHub Actions scheduler — evaluates it every minute and fires the associated command when every field matches the current date and time.
The expression "0 9 * * 1-5" reads as: minute 0, hour 9, any day of the month, any month, Monday through Friday — in plain terms, 09:00 on every weekday. The power of the format is that complex schedules collapse into a single, greppable line of configuration that lives in your repo alongside the job it describes.
The five fields
Each cron expression has five fields in a fixed order: minute (0-59), hour (0-23), day of month (1-31), month (1-12), and day of week (0-7, where both 0 and 7 mean Sunday, and the days run Monday through Saturday as 1-6). Each field accepts a value, a list, a range, a step, or a wildcard.
Order matters because the fields narrow from coarse to fine: hour constrains when within the day, minute when within the hour. Day of month and day of week interact specially — when both are restricted (not *), the job runs when either matches, not both. "0 0 1 * 1" means the first of the month or every Monday, whichever comes first, which trips up most people learning cron.
Special characters
The asterisk (*) means "every value in this field" — * in the minute field is every minute. A comma lists multiple values, so "0,30 * * * *" fires at minutes 0 and 30. A hyphen defines a range: "9-17" in the hour field covers the 9-to-5 workday.
A slash steps through a range: "*/15 * * * *" means every 15 minutes, and "0 */2 * * *" means at minute 0 of every second hour. Some implementations also support a few extras — L for the last day of the month, W for the nearest weekday, and # for the nth occurrence of a weekday, like "0 9 * * 2#2" for the second Tuesday at 09:00.
Common presets
Because five-field expressions are error-prone, most modern schedulers accept named presets that expand to the full form. @yearly (or @annually) is 0 0 1 1 *, @monthly is 0 0 1 * *, @weekly is 0 0 * * 0, @daily (or @midnight) is 0 0 * * *, and @hourly is 0 * * * *.
Presets are a readability win and a safety win: "@daily" is unambiguous, while the equivalent five fields are easy to get wrong. GitHub Actions, Spring, node-cron, and Quartz all support them. Check your scheduler's documentation for its full list — some add @reboot or @every_30m — and prefer a preset whenever it expresses what you mean.
Cron examples and patterns
"*/5 * * * *" runs every five minutes — the classic polling interval. "0 9 * * *" runs at 09:00 every day. "0 9 * * 1-5" runs at 09:00 on weekdays only. "0 0 * * 0" runs at midnight every Sunday, a common slot for weekly reports and cleanup jobs.
A few more workhorses: "0 0 1 * *" fires on the first of every month, handy for billing; "30 2 * * *" covers the classic 02:30 backup window when systems are quiet; "0 */6 * * *" runs every six hours for cache refreshes. Whatever schedule you write, verify it with a cron parser before deploying — a miswritten field like "61 * * * *" silently never runs, and "0 0 31 2 *" is valid cron that will simply never fire.