The five fields of a cron expression
┌─────────── minute (0-59) │ ┌───────── hour (0-23) │ │ ┌─────── day of month (1-31) │ │ │ ┌───── month (1-12 or JAN-DEC) │ │ │ │ ┌─── day of week (0-6, Sun=0; some systems also accept 7=Sun) │ │ │ │ │ * * * * *
Each field accepts: a single value (5), a range (1-5), a list (1,3,5), a step (*/15, "every 15"), or a combination (0-30/5). The asterisk * means "any value". Most modern cron implementations also accept the shorthands @yearly, @monthly, @weekly, @daily, @hourly, and @reboot.
The day-of-month / day-of-week trap
The single most common cron bug: when both "day of month" and "day of week" are set to non-asterisk values, traditional cron treats them as OR, not AND. So 0 0 1 * 1 doesn't mean "the 1st of the month, only if it's a Monday" — it means "the 1st of every month, OR every Monday". Three jobs a month most of the time, more when those overlap.
To get AND semantics, keep one of the two fields as * and filter the other side in your script. Or use a scheduler that supports the AND interpretation explicitly (Quartz, AWS EventBridge, Cron Job Organizer in n8n, etc.).
Time zones
Cron runs in the local time zone of the server it's running on — unless the runtime explicitly says otherwise. This is fine for personal Linux boxes; it's a recurring footgun in cloud environments where the server's clock might be UTC even if you mentally compose schedules in your local time.
- Linux crond / vixie-cron: uses the system's
/etc/localtime. SetCRON_TZ=America/New_Yorkat the top of a crontab to override per-file. - AWS EventBridge / CloudWatch Events: always UTC. No way to set a TZ in the expression itself.
- GitHub Actions: always UTC. Schedule expression is evaluated in UTC regardless of where you live.
- Kubernetes CronJob: respects the
spec.timeZonefield (introduced in 1.27). Without it, defaults to the kube-controller-manager's local time. - Node.js (node-cron): takes an optional
timezoneparameter; without it, the OS clock applies.
Daylight Saving Time: cron does not handle DST gracefully. A job scheduled for 2:30 AM during a spring-forward will be skipped (that time doesn't exist that day). During fall-back, it can run twice (the hour repeats). For DST-sensitive jobs, schedule in UTC and offset in your code.
Non-standard extensions you'll see in the wild
- Seconds field — Quartz (Java) and some schedulers add a 6th field at the start for seconds, allowing sub-minute precision:
*/30 * * * * *= every 30 seconds. Linux cron has minute resolution only. Lfor "last" — Quartz and AWS EventBridge acceptLin the day-of-month field for "last day of the month", and in the day-of-week field for "last weekday".Wfor nearest weekday — Quartz/AWS:15Wmeans "the weekday closest to the 15th".#for nth weekday — Quartz/AWS:2#3means "the third Tuesday".?as "no specific value" — Quartz/AWS use?instead of*in either the day-of-month or day-of-week field when the other is set, to avoid the OR-confusion described above.
This tool implements standard 5-field cron. If you paste a Quartz or AWS-EventBridge expression with L, W, or ?, it'll error. For those, use the platform's own validator (AWS Console has one built in).
Common pitfalls
- Empty PATH. Cron jobs run in a minimal environment —
$PATHis short,$HOMEmay not be set the way you expect. Use absolute paths to binaries (/usr/bin/python3, notpython3), or set PATH at the top of the crontab. - Silent failures. Cron mails stderr by default — but only if there's a working local mailer. On modern cloud VMs there usually isn't. Redirect explicitly:
command 2>&1 | logger -t myjobor to a logfile. - Overlapping runs. If your job takes longer than the interval, cron will start the next instance before the previous one finishes. Wrap with
flockor a similar mutex:flock -n /tmp/myjob.lock command. - The job doesn't exist.
service cron statusreturning "active" doesn't mean your job is running — your crontab entry might still be wrong. Aftercrontab -e, check thatcrontab -lshows the entry. Check the system log (journalctl -u cronor/var/log/cron) for parse errors. - 0:00 vs 12:00 confusion. Cron uses 24-hour time.
0 12 * * *is noon, not midnight. Midnight is0 0 * * *.
Common use cases
- Debug a cron schedule before deploying it
- Translate inherited cron expressions to plain English for documentation
- Verify a cron will fire at the times you expect across a DST boundary
- Sanity-check AWS EventBridge / GitHub Actions schedules (UTC mode)
Frequently asked questions
Day-of-month and day-of-week together — what happens?
Classic cron treats them as <strong>OR</strong>, not AND. <code>0 0 1 * 1</code> runs on the 1st of every month <strong>or</strong> every Monday — not "the 1st only if it's a Monday". To get AND behavior, keep one field as <code>*</code> and filter in your script.
Does it support the seconds field (Quartz / Spring)?
No — only standard 5-field cron. Quartz adds a 6th field for seconds (<code>* * * * * *</code>), which this tool doesn't parse. Same for Quartz extensions like <code>L</code>, <code>W</code>, <code>?</code>, <code>#</code>.
How are timezones handled?
Next-run times use your browser's local timezone by default. Toggle "show UTC" for the GMT-equivalent — useful for AWS EventBridge and GitHub Actions, which run in UTC regardless of your location.
Why does my schedule skip a run twice a year?
Daylight Saving Time. Cron doesn't handle DST transitions gracefully — a job at 2:30 AM during spring-forward simply doesn't run that day (the time doesn't exist); during fall-back, it can run twice. For DST-sensitive jobs, schedule in UTC.