Cron Expressions for People Who Hate Cron Expressions
A cron expression is 5 fields, each a number, a wildcard, or a step. Once you know what each field means and how the steps work, the expressions stop looking like a typo and start looking like a tiny schedule. Here is the cheat sheet, in plain language.
A cron expression is 5 fields: minute, hour, day-of-month, month, day-of-week. Each field is a number, a wildcard (*), a step (*/5), a range (1-5), or a list (1,3,5). The fields combine to mean "fire at this time, on these days." The 6 most common expressions cover 80% of real schedules. The <a href="/cron-generator">Cron Generator</a> on this site lets you build an expression by clicking what you want; the <a href="/cron-parser">Cron Parser</a> tells you when an existing expression will fire next.
Cron expressions look like a typo from a 1970s terminal. They are 5 space-separated fields, each a number or a wildcard, and they mean "fire at this time, on this day." The first time you see one, it is unreadable. The fifth time, it is a tiny schedule. Here is the cheat sheet, in plain language, with the 6 expressions that cover 80% of real schedules.
The 5 fields, in plain language
A cron expression has 5 fields:
* * * * *
│ │ │ │ │
│ │ │ │ └─ day of week (0-6, Sunday=0 or 7)
│ │ │ └─── month (1-12)
│ │ └───── day of month (1-31)
│ └─────── hour (0-23)
└───────── minute (0-59)
Read left to right: at this minute, at this hour, on this day-of-month, in this month, on this day-of-week. All 5 must match for the job to fire. If a field is *, it matches any value (so * * * * * means "every minute, of every hour, of every day, of every month, of every day of the week" — i.e. every minute, period).
The 4 wildcards and what they mean
Each field accepts 4 kinds of values:
*— any value. The field is not restricted.N— exactly this value.5in the minute field means "the 5th minute."*/N— every Nth value.*/5in the minute field means "minutes 0, 5, 10, 15, ..., 55."A-B— a range.9-17in the hour field means "hours 9, 10, 11, ..., 17."A,B,C— a list.1,15in the day-of-month field means "the 1st and the 15th."
Combinations work too: 1-5,20-23 in the hour field means "hours 1, 2, 3, 4, 5, 20, 21, 22, 23." */15 in the minute field is shorthand for 0,15,30,45.
The 6 expressions that cover 80% of real schedules
Here are the 6 cron expressions I have used most often in 20 years of building software. If you know these 6, you can schedule almost anything.
1. Every minute
* * * * *
Use for: a job that needs to run constantly, like a queue worker, a heart-beat, or a thing that polls. Be careful: every minute is 1,440 runs per day, 43,200 per month. If the job is expensive, the cost adds up fast.
2. Every 5 minutes
*/5 * * * *
Use for: a sync job, a poll, a thing that needs to be near-real-time but does not need to be exactly real-time. Runs 288 times per day, 8,640 per month. The most common production cron expression.
3. Every hour, on the hour
0 * * * *
Use for: a cleanup job, a cache refresh, a report aggregator. Runs 24 times per day. The 0 is the minute; the * in the hour field is "every hour." So this means "at minute 0 of every hour, of every day."
4. Every day at 3 AM
0 3 * * *
Use for: nightly batch jobs, database vacuuming, log rotation, the heavy stuff that should not run during business hours. Runs once per day. The 3 AM is arbitrary but conventional — it is late enough that no one is using the system, early enough that the job is done by morning.
5. Every Monday at 9 AM
0 9 * * 1
Use for: weekly reports, Monday-morning emails, weekly digest jobs. Runs once per week. The 1 in the day-of-week field is Monday (Sunday is 0 or 7). This is the expression for "every Monday morning, give me the summary of last week."
6. First of the month at midnight
0 0 1 * *
Use for: monthly billing, monthly reports, monthly retention of old data. Runs once per month. The 0 0 in the hour and minute fields is midnight. The 1 in the day-of-month field is the first. The * in the month field is "every month."
The 4 expressions that look weird but are useful
Beyond the 6 above, here are 4 that come up in specific cases.
Weekdays only at 8 AM
0 8 * * 1-5
The 1-5 in the day-of-week field is "Monday through Friday." Use for: a daily report that should only run on business days, an email that should not go out on weekends.
Every 15 minutes during business hours
*/15 9-17 * * 1-5
Minutes 0, 15, 30, 45 (the */15); hours 9 through 17 (the 9-17); Monday through Friday (the 1-5). Use for: a job that should run frequently during business hours and not at all outside them. Saves cost compared to every 15 minutes 24/7.
Twice a day, 6 AM and 6 PM
0 6,18 * * *
The 6,18 in the hour field is "6 and 18." Use for: a daily sync that needs to happen at the start of each work shift, a thing that should be fresh twice a day.
Every Sunday at 2 AM
0 2 * * 0
The 0 in the day-of-week field is Sunday. The 2 AM is the conventional "Sunday morning, before anyone is awake" time. Use for: weekly maintenance, weekly backups, the heavy jobs that should not interfere with the work week.
The 4 things that trip people up
1. The day-of-month and day-of-week interaction
When both the day-of-month and the day-of-week are not *, the job fires if EITHER matches. This is the opposite of what most people expect. 0 0 1 * 1 means "midnight on the 1st of the month OR midnight on Monday," not "midnight on the 1st of the month AND only if it is a Monday." If you want both, you have to write a wrapper script that checks the day of the week and exits early if it does not match.
2. The "every 5 minutes" off-by-one
*/5 in the minute field means minutes 0, 5, 10, 15, ..., 55. It does NOT mean minutes 1, 6, 11, 16, ..., 56. The first value is always 0. If you want the job to fire at minute 1, 6, 11, ..., you have to write 1-56/5 in the minute field, or use a different step pattern.
3. The timezone
Cron expressions do not have a timezone. The cron daemon runs in the system timezone, which is usually UTC on servers but can be anything. A job that fires "at 3 AM" in the cron expression fires at 3 AM in the system timezone, which may not be 3 AM in your local timezone. The fix is to either set the system timezone to UTC and write all expressions in UTC, or to use a tool that supports timezone-aware scheduling (which standard cron does not).
4. The "fire at the next valid time" semantics
Cron jobs fire at the next valid time after the daemon starts, and at every valid time after that. They do not "catch up" if the system was down. If the system is off at 3 AM Monday, the Monday 3 AM job does not run when the system comes back up. It just runs at the next 3 AM, which is Tuesday. This is a feature, not a bug, for most jobs. For jobs that must run exactly once, a separate scheduler with run-history is required.
How to test a cron expression
The two most common things to test are (a) "when does this expression fire next?" and (b) "did I get the fields right?" The Cron Parser on this site answers both: paste an expression, see the next 5 fire times and the human-readable breakdown of each field. The Cron Generator does the reverse: click what you want (every 5 minutes, weekdays only, etc.) and get the expression.
The Time Zone Converter and Unix Timestamp Converter on this site help with the timezone and timestamp gotchas. The Countdown Timer is a useful way to wait for a specific cron job to fire if you are debugging it.
The honest summary
A cron expression is 5 fields: minute, hour, day-of-month, month, day-of-week. Each field is a number, a wildcard, a step, a range, or a list. The 6 most common expressions (every minute, every 5 minutes, every hour, every day at 3 AM, every Monday at 9 AM, first of the month at midnight) cover 80% of real schedules. The 4 things that trip people up are the day-of-month/day-of-week OR-semantics, the off-by-one on */N, the implicit timezone, and the no-catch-up rule. The Cron Generator on this site builds an expression by clicking; the Cron Parser explains an existing expression. The whole thing is a 30-minute topic that takes 5 minutes once you see the 5 fields as a tiny schedule.