Developer Tutorials • 9 min read
Cron Expression Cheat Sheet: Fields, Examples and Traps
A crontab line is five columns of numbers and nobody memorises them. Here is the whole syntax read one column at a time, the expressions that cover almost every real job, and the six places where what you wrote is not what the server runs.
Read the Five Columns Left to Right
Every standard crontab schedule is five space-separated fields in a fixed order, and the column order is the source of most mistakes: minute, hour, day of month, month, day of week. The first number you write is never the hour. The line `0 9 * * *` means 09:00 every morning because the minute field takes the 0 and the hour field takes the 9.
Each field accepts one of five shapes: a single value, a comma-separated list, an inclusive range written `start-end`, a step written `*/n`, or an asterisk meaning every value the field allows. Any expression where all five columns contain one of those shapes is a valid schedule, which is why a broken cron line rarely produces an error, it just quietly never fires.
The ranges are the ones you would guess off a clock, with one exception worth memorising. Day of week runs 0 to 6 and 0 is Sunday, so a weekly job scheduled for Sunday is `0 0 * * 0`. Most cron implementations also accept 7 for Sunday, but the number you should copy into a line other people will read is 0.
| Column | Legal values | An asterisk here means | Worked example |
|---|---|---|---|
| 1. Minute | 0-59 | every minute of the matching hour | `30` fires at half past |
| 2. Hour | 0-23 | every hour of the matching day | `9` fires at 09:00 |
| 3. Day of month | 1-31 | every day of the matching month | `1` fires on the first |
| 4. Month | 1-12 | every month of the year | `1,4,7,10` fires quarterly |
| 5. Day of week | 0-6, Sunday is 0 | every weekday and weekend | `1-5` fires Monday to Friday |
Note: The generator on this site requires exactly five columns. A six-field expression with a leading seconds column (Quartz, Spring and the Node cron library all use one), a seven-field expression, and shorthand strings like `@daily` or `@reboot` come back as invalid format.
Fifteen Expressions That Cover Almost Every Real Job
These are the schedules worth copying, all of them parsed and previewed by the generator on this site rather than quoted from memory. The first ten are the presets built into the tool; the rest are the combinations people reach for once the basics are in place.
Read them as sentences, not as symbols. `*/15 * * * *` is every fifteen minutes, `0 9 * * 1` is nine in the morning on Monday, and `0 0 1 1,4,7,10 *` is midnight at the start of January, April, July and October.
| Expression | Plain English | Fires at |
|---|---|---|
| `* * * * *` | every minute | 60 times a day |
| `*/5 * * * *` | every five minutes | :00, :05, :10 and so on |
| `*/15 * * * *` | every fifteen minutes | :00, :15, :30, :45 |
| `0 * * * *` | on the hour | every hour at :00 |
| `0 0 * * *` | daily at midnight | 00:00 every night |
| `0 9 * * *` | daily at nine | 09:00 every morning |
| `0 9 * * 1-5` | weekdays at nine | Mon to Fri, 09:00 |
| `0 9 * * 1` | every Monday at nine | one run a week |
| `0 0 * * 0` | Sunday midnight | start of the week |
| `0 0 1 * *` | first of the month | 00:00 on the 1st |
| `0 0 1 1,4,7,10 *` | quarterly | 00:00 on Jan, Apr, Jul, Oct 1st |
| `30 2 * * *` | daily at 02:30 | the usual backup window |
| `0 */6 * * *` | every six hours | 00:00, 06:00, 12:00, 18:00 |
| `*/10 9-17 * * 1-5` | every ten minutes in office hours | 09:00 to 17:50, Mon to Fri |
| `15 3 * * 6` | 03:15 on Saturday | one quiet weekly slot |
How Steps, Lists and Ranges Combine
A step is a divisibility test, which is the easiest way to predict what `*/n` will do. `*/15` in the minute field means minutes divisible by 15, so :00, :15, :30 and :45. `*/6` in the hour field means hours divisible by 6, so 00, 06, 12 and 18. Put a fixed minute in front of an hourly step and you get a schedule with an offset: `30 */6 * * *` fires at 00:30, 06:30, 12:30 and 18:30.
Lists are unions and ranges are inclusive at both ends, and the two can appear in different columns of the same line. `0 0 1,15 * 1-5` says: minute 0, hour 0, the 1st or the 15th, any month, and only when the day of week is Monday through Friday.
When a schedule cannot be written as one field, write two lines instead of getting clever. Every ninety minutes is a good example, because ninety does not divide the hour and no single field can carry it. The day works out to sixteen slots that alternate between the hour and the half hour:
- `0 0,3,6,9,12,15,18,21 * * *` fires on the exact hours of that list.
- `30 1,4,7,10,13,16,19,22 * * *` fires on the half hour of the hours in between.
- Those two lines together give 00:00, 01:30, 03:00, 04:30 and onward in ninety-minute steps, and they wrap the day exactly: sixteen runs, and 22:30 plus ninety minutes is midnight.
- Anything you cannot express cleanly is usually a sign the job wants a systemd timer or a queue with a delay, not a crontab.
Note: One shape real cron accepts and this preview does not: a range with a step, written `10-30/5`. The preview reads it as a range whose upper bound is the text `30/5`, matches nothing, and shows an empty upcoming list without warning. Write the list out instead.
Where a Preview and a Real Server Disagree
This is the section that saves the most time, because every item here produces a schedule that looks correct on screen and behaves differently in production.
First, the two day columns. In Vixie-style cron, which is what most Linux distributions ship, day of month and day of week are combined with OR whenever both are restricted: `0 0 13 * 5` fires on the 13th of every month and also on every Friday. The generator here applies the ordinary AND, and its plain-English line says on day 13 of the month and on Fri. If a schedule mixes both columns, read the man page for the cron you actually run before trusting either answer.
Second, the clock. The upcoming-run preview walks the clock in your browser, while crontab fires on the clock of the machine running cron. A server on UTC that you think of as local time is five or more hours away from your desk, so `0 9 * * *` can land at 04:00 your time. Check the server with `timedatectl` and schedule against that, not against your tab.
Third, daylight saving. On a spring-forward night the local times between 02:00 and 03:00 do not exist, so a job at 02:30 is skipped; on the fall-back night they occur twice and many crons fire once, some fire twice, and the answer depends on the implementation and on whether the daemon was restarted. A minute-by-minute preview cannot show either outcome.
Fourth, Sunday written as 7. Real cron accepts 0 and 7 for Sunday; the preview matches only 0, so a line written with 7 shows nothing coming up. Use 0 and the line is portable and previewable.
Fifth, month and weekday names. crontab(5) allows JAN, FEB and MON, TUE in the text fields, which is handy in a hand-edited file and unreadable by this generator, which expects numbers. Keep numbers in anything another person has to maintain.
Sixth, the preview window. The search steps one minute at a time and stops after 50,000 iterations, which is about 34 days ahead. A yearly schedule is still a correct schedule, but its next run may sit outside the window and simply not appear in the list.
From Expression to a Line You Can Paste
The five columns are only half of a crontab entry. The rest is the command, and the command is where jobs actually fail. The generator appends whatever you type in the command box to the verified expression, then offers three output modes, so use it to produce the whole line rather than the schedule alone.
- Open your own crontab — `crontab -e` edits the crontab for the user running the command, one job per line. It is not the same file as `/etc/crontab`, and files in `/etc/cron.d` carry an extra user column between the time fields and the command, which is the single most common reason a pasted line does nothing.
- Use absolute paths everywhere — cron runs with a minimal environment, so a bare `python3 script.py` depends on a PATH you do not control and a working directory that is usually the home directory. The generator's default command, `/usr/bin/python3 /app/tasks/backup.py`, is written the way the line has to be written: interpreter by path, script by path.
- Decide what happens to the output — The three modes are append, discard and standard. Append puts `>> /var/log/cron-job.log 2>&1` on the line so stdout and stderr both land in a file you can read. Discard puts `> /dev/null 2>&1`, which is right for noisy jobs you do not want to keep. Standard leaves the line bare, and cron then mails whatever the job printed to the owner of the crontab, which only helps if mail is actually configured on that box.
- Say which shell when you need one — A job that depends on your login profile, version manager or aliases needs an explicit shell: `/bin/bash -lc 'your command'`. Without it you get a plain `sh` with almost nothing loaded.
- Confirm the first run before you trust the schedule — Watch for a timestamped line in the log, check the daemon's own record with `grep CRON /var/log/syslog` or `journalctl -t cron`, and have the job touch a heartbeat file a monitor can age out. A silent success and a silent failure look identical from outside.
Note: The previewed times and the copied line are produced in the page from your own browser state. Nothing about your schedule, your paths or your command leaves it, which matters more for a crontab than for most tools: those lines describe where your production jobs live.
Schedules That Look Right and Are Not
Five more ways a correct-looking line behaves differently from the intention behind it. None of these are errors, which is exactly why they survive code review.
- `0 0 31 * *` is not monthly. It fires on the 31st, and only seven months have one, so you get seven runs a year: January, March, May, July, August, October and December.
- `0 0 30 2 *` never fires at all. February 30th does not exist, and cron does not complain.
- `*/5 * * * *` starts a new copy every five minutes whether or not the previous copy finished. A backup that sometimes takes eight minutes will overlap; guard it with `flock -n /tmp/backup.lock your-command`.
- `0 9 * * 1-5` knows nothing about public holidays, your office calendar, or the fact that the person who reads the report is on leave.
- A minute of 60 or an hour of 24 is out of range and will not be caught by a five-column check, because the column count is right and only the value is wrong.
Questions About Cron Syntax and Scheduling
- What does 0 0 * * * mean in a crontab?
It fires at 00:00 every day. The minute field is 0, the hour field is 0, and all three day and month columns are asterisks, so nothing restricts which days qualify. Midnight in which timezone is decided by the server running cron, not by the line.
- What does 0 9 * * 1 do?
09:00 every Monday. The final column is day of week, where 0 is Sunday and 1 is Monday, so this is the shortest correct way to write a weekly weekday morning job. Real cron also accepts MON in that field, but numbers are what this generator reads.
- How do I run a job every five minutes?
`*/5 * * * *`. The minute field is a step, so it matches every minute divisible by 5: :00, :05, :10 through :55. If the job sometimes takes longer than five minutes, add a lock, because cron will happily start a second copy while the first is still running.
- Can cron run something every 90 minutes?
Not in one line, because the minute field resets when the hour rolls over and 90 minutes does not divide an hour. Sixteen runs a day do land on exact hour and half-hour boundaries, so two readable lines get you there: one with the minute fixed at 0 over the hours you want, and one with the minute fixed at 30 over the hours that complete the cadence.
- Does crontab support seconds?
Standard Unix and Linux crontab has no seconds field; its shortest interval is one minute. Quartz, Spring and several job frameworks use a six or seven field format that starts with seconds, and those lines are rejected by a normal crontab. Systems that need sub-minute scheduling generally want a timer or a resident worker rather than cron.
- My cron job did not run. Where do I look first?
In this order: is the cron daemon actually running, especially inside a container; does `crontab -l` show the line for the user you think you edited; are the paths absolute and executable by that user; is the server clock the timezone you assumed; and is output going somewhere you can see. A job that runs but discards its errors and a job that never ran look the same from outside.
Where These Numbers Come From
Keep Reading
Three guides that pick up where this one leaves off:
- The Ultimate Regular Expression (RegEx) Cheat Sheet for 2026 — Both grammars are a handful of small tokens that misread the same way, and neither one warns you when it matches nothing.
- JWT Tokens Explained: Decoding, Security & Best Practices — The most common three-in-the-morning crontab is a token refresh, and this is the file it renews.
- The Ultimate Guide to Client-Side Web Privacy: In-Browser Tools & Zero Server Uploads — Testing a schedule means pasting real paths into a page; here is what does and does not leave the tab.