Cron Syntax Explained: A Complete Guide to Crontab Expressions
A cron expression is the small piece of text that tells a scheduler “run this job at these times.” The whole language fits in five fields, but those five fields hide a surprising number of shortcuts – and one rule that quietly breaks schedules for thousands of people every year. This guide walks through all of it so you can read any crontab line at a glance and write your own with confidence. To test anything as you go, paste it into the cron expression generator on the home page.
The five fields, in order
Every standard cron line has exactly five space‑separated fields. From left to right they are minute (0–59), hour (0–23), day of month (1–31), month (1–12) and day of week (0–7). The classic mnemonic is “My Husband Drinks Milk Daily” – Minute, Hour, Day, Month, Day‑of‑week. If you only ever remember one thing, remember the order, because every other mistake flows from getting a value into the wrong column.
So 30 8 * * * reads as “at minute 30 of hour 8, every day” – that is 8:30 every morning. The two asterisks for day‑of‑month and month mean “any,” and the final asterisk means “any day of the week.”
The five special characters
Inside any field you can use five operators, and together they cover almost everything you will ever need.
The asterisk * means “every value” for that field. In the hour column it means every hour; in the month column, every month.
The comma builds a list. 0 8,12,18 * * * runs at 08:00, 12:00 and 18:00 – three fixed times a day. Lists are perfect when the times are not evenly spaced.
The hyphen builds a range. 0 9-17 * * * runs once an hour from 9am through 5pm inclusive. Ranges are inclusive at both ends, so 9–17 is nine separate hours.
The slash sets a step. */15 in the minute field means “starting at 0, every 15 minutes” – :00, :15, :30, :45. You can combine a step with a range too: 0-30/10 means minutes 0, 10, 20 and 30. A common mistake is to read */15 as “15 minutes after the job last ran”; it is not relative to anything, it simply divides the clock.
Finally, names are allowed in two fields. Months accept JAN through DEC and days accept SUN through SAT, so 0 0 * * SUN is the same as 0 0 * * 0. Names are case‑insensitive and can be clearer for human readers.
Steps and ranges in practice
Most real schedules are some mix of the operators above. “Every five minutes during business hours on weekdays” becomes */5 9-17 * * 1-5: every fifth minute, across the nine‑hour 9–17 range, on days 1 through 5 (Monday to Friday). “Twice a day at 6am and 6pm” is 0 6,18 * * *. “Every quarter hour” is */15 * * * *. Building these by editing one field at a time, and checking the plain‑English readout, is far safer than typing the whole line from memory.
The day-of-month vs day-of-week trap
This is the rule that catches almost everyone. When both the day‑of‑month field and the day‑of‑week field are restricted (neither is *), standard Vixie cron runs the job when either condition is true – not both. So 0 0 13 * 5 does not mean “midnight on Friday the 13th.” It means “midnight on the 13th of the month, or any Friday.” If you genuinely want Friday the 13th, you cannot express it in a single classic cron line; you typically schedule it for every 13th and add a guard in your script that exits unless the weekday is Friday. Our generator implements the real OR behaviour, so the next‑run preview shows you exactly what the scheduler will do.
Time zones and missed runs
Classic Unix cron runs in the server’s local time zone, which means daylight‑saving changes can cause a job to run twice or be skipped on the changeover night. Cloud schedulers often sidestep this by running in UTC – GitHub Actions, for instance, interprets its cron field as UTC. Because the time zone lives outside the expression, the same five fields can fire at different wall‑clock moments on different systems. The preview in our tool always shows your browser’s local time, so compare that against where the job actually runs.
Non-standard extras
Many schedulers add conveniences that are not part of the original five‑field standard. Shorthand macros like @daily, @hourly, @weekly and @reboot stand in for common patterns. Quartz (used in the Java world) adds a seconds field at the front and special characters such as L for “last,” W for “nearest weekday” and # for “nth weekday of the month.” These are powerful but not portable, so check your scheduler’s documentation before relying on them. The platform comparison covers which features work where.
A quick reading checklist
When you meet an unfamiliar cron line, read it in this order: first the day fields (is it daily, weekly, monthly?), then the hour, then the minute. 0 2 * * 0 read this way is “Sundays, at hour 2, minute 0” – 2am every Sunday. Working coarse‑to‑fine keeps you from misplacing a value. And whenever you are unsure, paste it into the generator and let the next‑run list confirm your reading. A schedule that looks right but runs at the wrong time is one of the most frustrating bugs to chase, precisely because nothing errors – the job just quietly fires when you did not expect it.