Cron is the oldest and most widely deployed job scheduler in computing — the original Unix cron shipped in 1975 and its expression syntax has been inherited essentially unchanged by every modern scheduling system, from Unix crontabs to Kubernetes CronJobs to cloud schedulers on AWS, GCP, and Azure. Despite that ubiquity, cron expressions are famously error-prone to write by hand, and a single wrong character can turn a daily job into a once-an-hour job that takes down a production system. The sections below explain the 5-field structure, the special characters that extend it, and the practical gotchas that catch even experienced engineers.
The 5-Field Structure and What Each Means
A standard cron expression has exactly 5 space-separated fields in a fixed order: minute, hour, day of month, month, day of week. Each field accepts a number within its valid range (minute 0–59, hour 0–23, day of month 1–31, month 1–12, day of week 0–6 with 0 = Sunday), a wildcard `*` meaning any value, or any of the special syntaxes described in the next section. The fields follow a chronological zoom-in pattern: minute is the most precise, day-of-week is the most general for weekly patterns. A tricky detail is the day-of-week field: historically different Unix variants disagreed on whether Sunday was 0 or 7, and the modern standard accepts both for compatibility. The month and day-of-week fields also accept three-letter abbreviations (`JAN`-`DEC`, `SUN`-`SAT`), which are case-insensitive and often more readable than numeric values. When both day-of-month and day-of-week are specified, most cron implementations use OR logic (the job runs when either matches), not AND logic (both matching required). This is one of the most common sources of confusion — `0 0 15 * FRI` runs at midnight on the 15th of every month AND every Friday, not only on Fridays that fall on the 15th.
Special Characters: Step, Range, List, and Wildcards
Four special characters extend the basic cron syntax and handle the majority of real-world scheduling needs. The asterisk `*` is the every-value wildcard: `* * * * *` fires every minute of every hour of every day. The slash `/` introduces step values: `*/15` in the minute field fires at minute 0, 15, 30, 45 — every 15 minutes. Steps can combine with ranges: `9-17/2` fires at hours 9, 11, 13, 15, 17. The hyphen `-` introduces ranges: `9-17` covers 9 AM through 5 PM inclusive in the hour field. The comma `,` introduces lists: `1,3,5` in the day-of-week field means Monday, Wednesday, Friday. These combine freely — `0 9,12,17 * * 1-5` fires at 9 AM, noon, and 5 PM on weekdays. Some cron implementations add additional specials beyond these basics. Quartz (used by many Java scheduling libraries) adds `L` (last day of month/week), `W` (nearest weekday), and `#` (nth occurrence of a day in a month). Jenkins adds `H` for hashed values that distribute identical schedules across time to avoid thundering-herd effects. This tool targets the standard 5-field format used by Unix crontab, AWS EventBridge, GCP Cloud Scheduler, Azure Logic Apps, and most popular scheduling libraries like node-cron and python-crontab.
Practical Gotchas That Catch Even Experienced Engineers
Several cron gotchas repeatedly cause production incidents even for teams that use cron every day. First, timezone handling: cron expressions run in the system's local timezone by default, which means DST transitions in March and November can cause jobs to skip a day or double-run depending on the transition direction. Always configure cron jobs to run in UTC where possible, or explicitly test DST transition weekends before deploying time-sensitive schedules. Second, the day-of-month vs day-of-week OR logic discussed above catches engineers expecting AND logic. If you want a job to run only on Fridays that fall on the 15th, you cannot express that in standard cron — you need to use AND logic in application code after the job fires. Third, leap-year edge cases: `0 0 29 2 *` runs only on February 29, which happens once every 4 years. Testing this once and seeing it appear to not work is easy. Fourth, `@reboot` and other specials like `@daily` or `@hourly` are supported by many implementations but are not portable — cloud schedulers generally don't accept them. Always use the explicit 5-field form for portability. Fifth, long-running jobs can overlap themselves: if a job runs every 5 minutes but takes 7 minutes to execute, two instances will run concurrently. Wrap with a lock file (`flock` on Linux) or use application-level coordination to prevent this.