The five fields, and the one that is not a field
A standard cron expression has five fields: minute, hour, day of month, month and day of week, in that order. Each accepts a specific value, a comma-separated list, a range with a hyphen, a step with a slash, or * for every value. So */15 9-17 * * 1-5 means every fifteen minutes between nine and five, Monday to Friday.
Confusion often comes from variants that add a sixth field. Quartz, used widely in the Java ecosystem, and Spring's scheduler put seconds first, so a six-field expression means something quite different from a five-field one — and pasting a Quartz expression into Unix cron shifts every field by one position, producing a schedule that runs at a plausible but entirely wrong time.
Day-of-week numbering also varies. Most implementations use 0 for Sunday, some accept 7 for Sunday as well, and Quartz uses 1 for Sunday, shifting the whole week. When a job runs a day off, this is usually why.
The day-of-month and day-of-week trap
This is the single most surprising rule in cron, and it catches experienced people. When both the day-of-month and day-of-week fields are restricted — neither is * — they combine with OR, not AND. The job runs when either matches.
So 0 0 1 * 1 does not mean "the first of the month, if it is a Monday". It means "the first of the month, and also every Monday" — roughly five times as often as intended. Every other pair of fields combines with AND, which makes this exception genuinely counter-intuitive.
If you need a true AND, cron cannot express it. The conventional workaround is to schedule the broader condition and test the narrower one inside the job — run every Monday, and exit immediately unless the date is also the first. Checking the plain-English description before deploying catches most instances of this.
Time zones, DST and missed runs
Cron runs in whatever time zone the system is configured for, which on servers is very often UTC while the person writing the schedule is thinking in local time. A report scheduled for "9am" then arrives at 9am UTC, which may be the middle of the night for its audience.
Daylight saving transitions break assumptions in both directions. When clocks go forward, the skipped hour does not exist, so a job scheduled at 02:30 simply does not run that day. When clocks go back, that hour occurs twice, and depending on the implementation the job may run twice. Neither is hypothetical, and both have caused duplicate billing runs and missing reports.
The robust approach is to run scheduled jobs in UTC and convert for display, and to make jobs idempotent so a duplicate execution is harmless. Avoid scheduling anything important between 01:00 and 03:00 local time, which is where transitions occur in most regions. The timezone converter helps work out what a UTC schedule means for each audience.