Two mainstream formats
The Linux/Unix standard uses 5 fields: minute hour day month weekday. Quartz (the Java/Spring world) uses 6 fields with an extra "seconds" in front. Confirm which one your platform expects first, otherwise the whole expression is shifted.
Special characters at a glance
*any value;?unspecified (day or weekday only);,list:0 0 1,15 * *on the 1st and 15th;-range:0 9-18 * * *from 9:00 to 18:59;/step:*/5every 5 units.
The two most common pitfalls
- Day and weekday both set: most implementations combine them with OR — the 1st OR Monday triggers, which is counter-intuitive. For "only the 1st", set weekday to
?. - Timezone: Cron runs in the server timezone. A UTC server while you are in UTC+8 means local 9:00 is
0 1 * * *.
Debugging tip
Instead of deploying repeatedly, compute the next few execution times with a parser and check them against your expectation — reversed fields show up in seconds.
Ready-to-use expressions
| Need | Expression (5 fields) |
|---|---|
| Every minute | * * * * * |
| Every 5 minutes | */5 * * * * |
| Daily at midnight | 0 0 * * * |
| Weekdays at 9:30 | 30 9 * * 1-5 |
| 1st of the month, 03:00 | 0 3 1 * ? |
| First day of each quarter | 0 0 1 1,4,7,10 * |
Job did not run? Check in this order
- Timezone: containers default to UTC — local 09:00 is
0 1 * * *; - The daemon: cron is not running, or the crontab vanished after a restart;
- Field count: the platform expects 6 fields (Quartz) and you supplied 5, or the reverse;
- A stray second field:
* * * * * *fires every second and can hammer the machine; - No logging: append
>> /var/log/job.log 2>&1to confirm the job is triggered at all.
A minimal pre-deploy check
Compute the next five execution times with a parser and compare them to your expectation — far faster than watching logs after deploy. This matters most across timezones: the same expression differs by 8 hours between UTC and UTC+8 servers.
A minimal pre-launch checklist
- Confirm the next 3–5 run times with a parser;
- Confirm the timezone (containers usually default to UTC) matches business time;
- Confirm the job is idempotent: running it twice causes no side effects, so replay is safe;
- Confirm logs are persisted and alerting works, so a failure surfaces immediately rather than through user complaints.
Real-world cases: three frequent incidents
- "Set for 9am but ran at noon": the container is in UTC while the business assumed UTC+8. Fix by converting the expression to
0 1 * * *, or set the container timezone (TZ=Asia/Shanghai) and restart. - "It ran on the 1st, but also every Monday": typing concrete values for both day and weekday turns the trigger into OR. Fix by keeping one and writing
?for the other. - "Works by hand, silent under cron": usually environment and PATH differ — cron does not load your shell profile. Fix by using absolute paths or sourcing an env file explicitly.
FAQ
Must one of day/weekday be "?" Not required, but recommended; when both are set the standard ORs them, which surprises people. Does the 6-field form start with seconds? Yes — Quartz order is second minute hour day month weekday [year], unlike Linux's five fields. Does */5 start at minute 0? Yes, it means 0,5,10,…. Can it express "last day of month"? Not in the standard five fields; use a platform extension (e.g. Quartz L) or a script check.
Try them: Cron parser, timestamp converter
cron versus systemd timers
- Expression style: systemd uses readable
OnCalendarvalues like*-*-* 09:00:00, clearer but not directly convertible; - Missed runs: cron simply skips a missed slot, while systemd can catch up after downtime with
Persistent=true; - Logs: cron output lands in system mail or syslog, systemd in
journalctl -u— different troubleshooting paths; - Sub-minute: cron's floor is one minute; for seconds use a systemd timer or in-app scheduling, never chained
sleep.
Timezone and daylight saving
cron interprets expressions in the system timezone, and containers usually default to UTC — the classic "9am locally, noon in production". Regions with DST also see a run happen twice or not at all on transition days, so set TZ explicitly and avoid scheduling critical jobs between 02:00 and 03:00.
Readability suggestions
Comment complex expressions or name them as constants, e.g. EVERY_15_MIN_ON_WEEKDAYS_WORKING_HOURS for */15 9-18 * * 1-5. Keep them in one managed place rather than scattered across service config files, or changing one schedule means searching every repository.