Never ship a cron expression because it looks plausible. Calculate its next several runs in the same timezone and cron implementation that will execute it. The expression 0 9 * * 1-5 means weekday mornings in traditional five-field cron; it does not magically mean “business days in every locale”.
Read the five fields, then read the caveat
# minute hour day-of-month month day-of-week
30 2 * * 1-5
That is 02:30 Monday through Friday. It is not “every 30 minutes”; a leading 30 names one minute of the hour.
| Need | Expression | Sanity check |
|---|---|---|
| Every 15 minutes | */15 * * * * | 00, 15, 30, 45 |
| Weekdays at 09:00 | 0 9 * * 1-5 | Monday to Friday only |
| First day each month | 0 0 1 * * | Not the first weekday |
| Sunday at 23:45 | 45 23 * * 0 | Confirm whether your cron accepts 0 or 7 for Sunday |
The day fields are the footgun
In Vixie-style cron, when both day-of-month and day-of-week are restricted, a job may run when either matches. So 0 8 1 * 1 can run on the first of the month and every Monday—not only on Mondays that are the first.
“First business day” is a business rule, not a clean five-field cron expression. Schedule daily, then have code decide whether today qualifies.
Treat timezone and DST as input
Many crontabs run in the host's local timezone; containers and managed schedulers often default to UTC. Put the intended timezone next to the schedule, and choose what happens when a local time occurs twice or not at all during daylight-saving changes.
# Run in UTC; application converts reporting boundaries explicitly.
0 0 * * * /app/bin/daily-rollup
UTC is less surprising for infrastructure work. For payroll, local trading hours, or legal deadlines, use a scheduler that documents timezone and daylight-saving semantics rather than pretending the server clock is a policy engine.
Before enabling it
Generate at least ten future run times, including a month boundary and a DST boundary if relevant. Confirm overlap behaviour too: cron starts a new process even if yesterday's job is still running. A lock—database advisory lock, queue uniqueness key, or flock—is usually the missing half of the schedule.
Cron is excellent at saying when. Make the job itself responsible for deciding whether it is safe to run.
