← Back to all articles

Cron Expressions Explained: 5 vs 6 Fields and Pitfalls

CronPitfalls

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: */5 every 5 units.

The two most common pitfalls

  1. 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 ?.
  2. 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

NeedExpression (5 fields)
Every minute* * * * *
Every 5 minutes*/5 * * * *
Daily at midnight0 0 * * *
Weekdays at 9:3030 9 * * 1-5
1st of the month, 03:000 3 1 * ?
First day of each quarter0 0 1 1,4,7,10 *

Job did not run? Check in this order

  1. Timezone: containers default to UTC — local 09:00 is 0 1 * * *;
  2. The daemon: cron is not running, or the crontab vanished after a restart;
  3. Field count: the platform expects 6 fields (Quartz) and you supplied 5, or the reverse;
  4. A stray second field: * * * * * * fires every second and can hammer the machine;
  5. No logging: append >> /var/log/job.log 2>&1 to 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

  1. Confirm the next 3–5 run times with a parser;
  2. Confirm the timezone (containers usually default to UTC) matches business time;
  3. Confirm the job is idempotent: running it twice causes no side effects, so replay is safe;
  4. Confirm logs are persisted and alerting works, so a failure surfaces immediately rather than through user complaints.

Real-world cases: three frequent incidents

  1. "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.
  2. "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.
  3. "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 OnCalendar values 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.