Backups that run at the wrong time, reports that never arrive, and surprise loads at midnight are often not bugs in your software — they’re misunderstandings of cron. When you see a five-field cron expression like 30 2 * * 1-5 copied from a Stack Overflow answer, you should know exactly what each field means, what it will and will not do on your system, and how to verify it before you trust it with a nightly backup or an hourly report.
Why five-field crons still matter
Most Unix-like systems (and many hosted Linux containers) still use the classic five-field crontab format: minute, hour, day-of-month, month, day-of-week. It’s compact and powerful, but that power comes with a few traps:
- Different cron implementations have slightly different behaviors (especially around day-of-month vs day-of-week).
- Timezones and daylight saving time (DST) introduce unexpected duplicate or missing runs.
- Using ranges, lists, and steps incorrectly leads to schedules that run far more often than intended.
Field order — what each position means
Read cron expressions left-to-right. The five canonical fields are:
- Minute (0-59)
- Hour (0-23)
- Day-of-month (1-31)
- Month (1-12 or JAN-DEC)
- Day-of-week (0-7 or SUN-SAT; both 0 and 7 usually mean Sunday)
For example, 30 2 * * 1-5 means “at minute 30, hour 2, every day of month, every month, on weekdays Monday–Friday.”
Wildcards, ranges, lists, steps — the building blocks
These tokens let you compactly describe times:
- * — wildcard: every allowed value.
- 1-5 — range: values from 1 through 5 inclusive.
- 1,3,5 — list: specific values.
- */10 — step: every 10 units (0,10,20, …).
- 1-20/3 — combined: every 3 units inside the 1–20 range.
Quick reference table
| Field | Range / Examples | Common notation |
|---|---|---|
| Minute | 0-59 | */5 = 0,5,10,… |
| Hour | 0-23 | 2 = 02:00 |
| Day-of-month | 1-31 | 1-7 = first week |
| Month | 1-12 or JAN-DEC | 6 = June |
| Day-of-week | 0-7 or SUN-SAT | 0 or 7 = Sunday |
Weekday vs day-of-month — a frequent gotcha
Many users expect jobs to run only when both day-of-month and day-of-week match (logical AND), but classic Vixie-style cron treats them as an OR: if either field is restricted (not *), a job runs when either matches. That means:
0 3 1 * 1runs at 03:00 on the 1st of the month and also every Monday at 03:00 — not only on Mondays that are the 1st.- If you meant “only on Mondays that are the 1st,” you need other tooling or a conditional wrapper in your script to check the exact date.
Assumption note: some cron implementations or cron-like schedulers (systemd timers, Quartz, enterprise schedulers) may behave differently. Always check your system’s documentation.
Timezone surprises and DST
Cron usually uses the system’s local time. That leads to two common surprises:
- On DST “spring forward,” an hourly job might skip one run because the clock jumps forward an hour.
- On DST “fall back,” jobs scheduled in the repeated hour may run twice.
Mitigations:
- Run cron in UTC to avoid DST shifts and convert display times in logs.
- Use schedulers that support explicit timezone fields (if your platform supports it).
- Log timestamps with timezone info and include run IDs to detect duplicates.
Missed runs and how to recover
If a machine is down during a scheduled time, classic cron will not play catch-up. Your backup that should have run at 02:00 won’t automatically run at 02:05 when the server restarts. Options to handle missed runs:
- Use a persistent job manager (systemd timers with Persistent=true) that will run missed jobs on boot.
- Have your job check the last successful run timestamp and perform one-time catch-up work if the gap is large.
- Use external scheduling services or managed cron-like services that guarantee run delivery.
Ordered workflow: create, verify, deploy a cron for backups or reports
- Define the business requirement: exact hours, weekdays, tolerance for duplicates or missed runs.
- Write a tentative cron expression in a safe place (not directly in production crontab).
- Translate the expression into plain English and confirm with a colleague or the Cron Expression Explainer tool.
- Test on a staging system with the same timezone settings and monitor actual runs for several cycles.
- Deploy to production and enable logging and alerts for non-runs or duplicated runs.
- Include a cron-run health check script that verifies last-run timestamps and file sizes for backups.
Verification checklist (quick)
- Confirm field order: minute hour day month weekday.
- Check if your cron treats DOM and DOW as OR (classic) or AND (rare).
- Decide whether to use system local time or UTC.
- Run the expression through a parser/explainer and test on staging.
- Add run-time guards (lockfiles, last-run checks) to your script.
Common mistakes
- Swapping hour and minute:
2 30 * * *is not the same as30 2 * * *. - Using comma-separated lists and forgetting spaces or commas:
0 0 1,15 * *is correct;0 0 1 15 * *is wrong (extra field). - Assuming DOM+DOW is AND when your system uses OR; this often causes twice-as-many runs.
- Relying on system reboot to catch up missed runs without a persistent scheduler.
Limitations and privacy considerations
Limitations:
- Five-field crons lack a year field; complex yearly schedules need additional logic.
- Implementation differences mean that syntax allowed on one host may be invalid on another.
- Edge-case behaviors (DST, leap seconds) depend on OS and cron implementation and are outside cron’s control.
Privacy and safety:
- Cron expressions themselves contain no secrets. But cron lines in crontab often call scripts that use credentials — treat crontab files as sensitive configuration and limit access.
- When testing or sharing crons, don’t paste production credentials or backup destinations in public forums.
Concrete examples
Example 1 — nightly backup at 02:30 every day:
30 2 * * *
Meaning: at 02:30 local system time, every day of month, every month, every weekday. To avoid DST issues, you might set the server TZ to UTC and schedule 30 2 * * * in UTC.
Example 2 — generate business reports every weekday at 06:00 and every 15 minutes during business hours (09:00-17:00):
# run at 06:00 Mon-Fri
0 6 * * 1-5 /opt/reporting/run_daily_report.sh
# run every 15 minutes from 09:00 through 17:45 Mon-Fri
*/15 9-17 * * 1-5 /opt/reporting/run_quick_stats.sh
Note: If you only want the 06:00 job to run on the first weekday of the month, classic cron alone cannot express that; add a check inside the script (e.g., test for date +%d and exit if not 01).
Using Cron Expression Explainer
The Cron Expression Explainer placeholder links to a utility that parses common cron expressions and renders them as human-readable schedules. Use it to:
- Confirm that your expression maps to the schedule you expect (plain English).
- Quickly validate syntax for typical five-field expressions.
What it will not do:
- Detect host-specific behavior like whether DOM+DOW are treated as OR or AND on your server.
- Guarantee how your production cron daemon handles DST, reboot persistence, or extended nonstandard tokens.
FAQ
Q — Why did my job run twice during fall DST?
A — During DST fallback, the clock repeats one hour. If a job is scheduled inside that hour and your cron uses local time, it can run twice. Use UTC or a scheduler that avoids local-time ambiguities if duplicates are unacceptable.
Q — Can cron run a job only the last Friday of each month?
A — Not directly with a single five-field expression. A common approach is to schedule a weekly Friday job and in the script check if the date falls within the last seven days of the month (for example, check if adding seven days changes the month). Alternatively, use an external scheduler that supports advanced calendar rules.
Q — How do I prevent overlapping runs of a long job?
A — Use a lockfile or use flock to ensure only one instance runs. Example wrapper:
*/10 * * * * /usr/bin/flock -n /var/lock/myjob.lock /opt/scripts/long_backup.sh
Sources
Editorial note: This guide is an educational overview. Confirm the output against the documentation and workflow that apply to your project.