Cron is one of the oldest and most ubiquitous scheduling systems in computing. Originally developed for Unix systems in the 1970s, cron jobs are now found in Linux servers, cloud platforms, CI/CD pipelines, Kubernetes clusters, and countless web applications. The cron expression format -a compact five-field syntax that encodes an arbitrarily complex recurring schedule -has remained largely unchanged for decades. Yet it trips up developers constantly. This guide explains every aspect of cron expressions clearly, with examples you can immediately apply.
The Five Fields of a Cron Expression
A standard cron expression consists of five space-separated fields. Reading left to right: the first field specifies the minute (0–59), the second specifies the hour (0–23), the third specifies the day of the month (1–31), the fourth specifies the month (1–12), and the fifth specifies the day of the week (0–7, where both 0 and 7 represent Sunday). A helpful mnemonic is 'minutes, hours, date, month, weekday.' Some systems extend this with a seconds field at the beginning and a year field at the end.
Memory trick: think of cron fields as zooming out in time -minutes → hours → days → months → weekdays. The most granular (seconds, minutes) comes first.
Special Characters -The Real Power of Cron
- **`*` (wildcard)** -Matches every valid value in the field. `* * * * *` means every minute of every day. Use when you do not want to restrict a field.
- **`,` (list)** -Specifies multiple values. `1,3,5` in the day-of-week field means Monday, Wednesday, and Friday. Can have as many values as needed.
- **`-` (range)** -Specifies an inclusive range. `9-17` in the hours field means every hour from 9 AM to 5 PM. Can be combined with list: `1-5,7`.
- **`/` (step)** -Specifies a step increment. `*/5` means every 5 units. `10/5` means every 5 units starting at 10. In minutes `*/15` means 0, 15, 30, 45.
- **`L` (last)** -In the day-of-month field `L` means the last day of the month. In the day-of-week field `5L` means the last Friday of the month. Not supported in standard Unix cron.
- **`W` (weekday)** -Nearest weekday to a given date. `15W` means the nearest weekday to the 15th. If the 15th is Saturday the job runs on Friday the 14th. Not in standard Unix cron.
- **`#` (nth weekday)** -`1#3` means the third Monday of the month. `5#2` means the second Friday. Supported in Quartz but not standard cron.
- **`?` (no specific value)** -Used in Quartz and AWS to say 'I do not care about this field.' Required in one of dom or dow when the other is specified.
Common Cron Expressions You Will Actually Use
- `* * * * *` -Every minute. Useful for high-frequency background tasks like queue processing.
- `*/5 * * * *` -Every 5 minutes. Good for polling or sync tasks.
- `0 * * * *` -At the top of every hour. Common for hourly reports or cache refreshes.
- `0 0 * * *` -Daily at midnight. Good for daily cleanup or batch processing.
- `0 9-17 * * 1-5` -Every hour from 9 AM to 5 PM on weekdays. For business-hours notifications.
- `0 0 1 * *` -First of every month at midnight. For monthly billing or reports.
- `0 0 * * 0` -Every Sunday at midnight. Good for weekly maintenance.
- `0 2 * * *` -Every day at 2 AM. A common time for backups and heavy maintenance.
- `30 6 */2 * *` -Every other day at 6:30 AM. For semi-frequent scheduled tasks.
- `0 0 1 1 *` -January 1st at midnight. For yearly operations.
DOM vs DOW -A Common Source of Confusion
One of the most frequently misunderstood aspects of cron is the relationship between the day-of-month (dom) and day-of-week (dow) fields. In standard Unix cron, if you specify a value in both fields (rather than `*`), the job runs when EITHER condition is true -it is OR logic, not AND. So `0 0 15 * 1` runs at midnight on the 15th of every month AND also every Monday at midnight, not on Mondays that fall on the 15th. If you want AND logic (only on Mondays that are also the 15th) you need to handle that in your script, not the cron expression. Quartz scheduler adds the `?` character specifically to address this -using `?` in one field means you are restricting by the other only.
Platform Differences You Need to Know
- Standard Unix/Linux cron -5 fields (min hour dom mon dow). Day-of-week 0 and 7 both mean Sunday. No L, W, or # characters.
- Quartz Scheduler (Java) -6 or 7 fields: seconds min hour dom mon dow [year]. Uses ? for no-specific-value in dom or dow. Day-of-week uses 1=Sunday convention (opposite of Unix).
- AWS EventBridge (CloudWatch Events) -6 fields including year. Requires ? in either dom or dow. Uses cron() wrapper syntax: `cron(0 12 * * ? *)`.
- GitHub Actions -Standard 5-field POSIX cron. Minimum interval is every 5 minutes. Schedules run in UTC.
- Kubernetes CronJob -Standard 5-field Unix cron. Times are in the timezone of the kube-controller-manager unless configured otherwise.
- Vercel Cron (vercel.json) -Standard 5-field. Minimum interval is every minute on Pro plans.
Always check which cron implementation your platform uses. Quartz day-of-week values are offset by 1 compared to Unix (Quartz 1=Sunday vs Unix 0=Sunday). A mistake here causes your job to run on the wrong day entirely.
Tips for Writing Reliable Cron Expressions
- Always test your expression with a tool before deploying -verify the next run times are exactly what you expect
- Be explicit about timezone -most cron systems use the server's local timezone or UTC. Document which one applies and set it explicitly if possible.
- Avoid scheduling many jobs at the same second -midnight (0 0 * * *) is the most common cron time. Consider offsetting by a few minutes to spread load.
- Add jitter for distributed systems -if multiple instances run the same cron, add randomized delay in your job code to avoid thundering herd
- Test edge cases -what happens at the end of month? February 29th? DST transitions?
- Log every execution -cron jobs often fail silently. Always log start, end, and any errors.