Cron Syntax Explained: What Those Five Asterisks Actually Mean
You've been there. You copy a cron expression from Stack Overflow — something like 0 2 * * 1 — paste it into your server config, and move on. It works. Magic. But three months later, your boss asks "why does this job run at 2 AM on Mondays?" and you're quietly Googling it again under the desk.
Let's fix that. For good.
Cron is one of those tools that looks like alien gibberish until someone takes five minutes to explain it properly. After that, it clicks forever. That's what this article is — those five minutes.
What Even Is Cron?
Cron is a job scheduler built into Unix/Linux systems. It lets you say: "Run this script at this specific time, on this schedule, forever." It's how your server sends that weekly email digest at 9 AM every Monday, or how your database backup runs quietly at 3 AM every night without you lifting a finger.
Every cron job has two parts: when to run (the cron expression) and what to run (the command). We're focused on the "when" part today, because that's where people get lost.
The Five Fields — Meet the Family
A cron expression has exactly five fields, separated by spaces. Just five. Here they are, left to right:
┌──────── minute (0–59)
│ ┌────── hour (0–23)
│ │ ┌──── day-of-month (1–31)
│ │ │ ┌── month (1–12)
│ │ │ │ ┌ day-of-week (0–6, where 0 = Sunday)
│ │ │ │ │
* * * * *
When all five are asterisks — * * * * * — it means "run every single minute of every hour of every day of every month, always." That's the wildcard. It means "I don't care about this field, match everything."
Now let's go through each field one by one, like you're explaining it to someone who just learned what a terminal is.
Field 1: Minute (0–59)
This is the most granular field. Valid values go from 0 to 59, representing the minute within an hour.
0 means "at the top of the hour" (e.g., 2:00, not 2:14).
30 means "at half past."
* means "every minute."
So 15 * * * * runs at :15 of every hour — 1:15 AM, 2:15 AM, 3:15 AM, and so on around the clock. If you've ever wondered why so many scheduled tasks seem to pile up at the top of the hour, it's because developers lazily write 0 in the minute field out of habit. Try 7 or 23 instead — it spreads the load.
Field 2: Hour (0–23)
This uses 24-hour time. Midnight is 0, noon is 12, 11 PM is 23.
0 2 * * * runs at 2:00 AM every day. (Minute 0, hour 2, rest are wildcards.)
0 9 * * * runs at 9:00 AM every day.
One confusion point: when you write * 2 * * * (asterisk in the minute field), that means "every minute of the 2 AM hour" — so 2:00, 2:01, 2:02, all the way to 2:59. That's almost never what people want. Usually they mean 0 2 * * *. Don't forget to set your minute field!
Field 3: Day of Month (1–31)
Pretty self-explanatory — which day of the calendar month? The 1st through the 31st.
0 9 1 * * — run at 9 AM on the 1st of every month. Your monthly report job.
0 0 15 * * — run at midnight on the 15th. Payroll cycle, perhaps.
Note that if you set day-of-month to 31 and the month only has 30 days, cron just skips that month. It won't run on the 30th instead. Cron is literal, not clever.
Field 4: Month (1–12)
Which months should this run? January is 1, December is 12. Some cron implementations also accept three-letter abbreviations like jan, feb, but sticking to numbers is safer across systems.
0 9 1 1 * — run at 9 AM on January 1st only. Your "Happy New Year" script.
0 0 * 12 * — run at midnight every day in December. Your holiday campaign scheduler.
Most jobs don't touch this field and just leave it as *, meaning "every month." But when you need seasonal logic — like running an end-of-quarter job in March, June, September, and December — this is your lever.
Field 5: Day of Week (0–6)
This is where newcomers get confused most often, because it overlaps conceptually with day-of-month. The days go 0 (Sunday) through 6 (Saturday). Some systems also accept 7 as Sunday, and some accept sun, mon, etc.
0 9 * * 1 — run at 9 AM every Monday. Your weekly report.
0 17 * * 5 — run at 5 PM every Friday. Your end-of-week backup.
0 10 * * 0,6 — run at 10 AM on Saturdays and Sundays. Comma = "and."
Here's the important nuance: if you specify both day-of-month and day-of-week, cron treats them with OR logic, not AND. So 0 9 1 * 1 means "run at 9 AM if it's the 1st of the month OR if it's Monday" — not "only on Mondays that fall on the 1st." That trips people up constantly. If you want a job only on the first Monday of the month, you actually need a workaround in your script, not just in the cron expression.
The Special Characters — More Than Just Asterisks
Beyond *, cron gives you a few more tools:
Comma ( , ) — list of values
0 9 * * 1,3,5 — run at 9 AM on Monday, Wednesday, Friday.
Hyphen ( - ) — range
0 9 * * 1-5 — run at 9 AM every weekday (Monday through Friday).
Slash ( / ) — step/interval
*/15 * * * * — run every 15 minutes. (Every 15th minute starting from 0: 0, 15, 30, 45.)
0 */6 * * * — run every 6 hours. (At 0:00, 6:00, 12:00, 18:00.)
The step syntax confuses people at first. Think of */15 as "starting at 0, then every 15 steps." It's a simple division. For hours, */4 hits 0, 4, 8, 12, 16, 20.
Let's Read Some Real Expressions
Time to put it together. Here are five expressions you'll encounter in the wild, decoded:
0 0 * * * — midnight every day. Classic daily backup schedule.
30 23 * * 5 — 11:30 PM every Friday. Someone's weekend deployment job (questionable life choices, but valid cron).
*/5 * * * * — every 5 minutes, all day, always. Health check pings, log flushes, that sort of thing.
0 8-18 * * 1-5 — every hour on the hour, from 8 AM to 6 PM, Monday through Friday. Office-hours only notifications.
0 0 1 */3 * — midnight on the 1st of every third month (January, April, July, October). Quarterly report generator.
A Quick Sanity Check Trick
Before you deploy any cron expression to production, use crontab.guru. It's a free online tool where you paste your expression and it translates it into plain English in real time. It'll tell you the next few scheduled run times too. There's genuinely no reason to guess — just verify.
Also: remember that cron runs in the timezone of the server, not your laptop. If your server is in UTC and you want something at 9 AM your time (IST, which is UTC+5:30), you need to do the math. 0 3:30 UTC = 9:00 AM IST, but cron doesn't take half-hours in the hour field, so you'd write 30 3 * * * (03:30 UTC = 09:00 IST). Timezone confusion is the number one reason "I set it for 9 AM" jobs run at weird hours.
The One Thing to Remember
If someone handed you * * * * * and said "what does this do?" — you now know. Every field is a wildcard. Runs every minute. Forever.
And if someone hands you 0 2 * * 1? Minute 0, hour 2, every day-of-month, every month, but only on Monday. That's 2:00 AM every Monday. Simple.
The pattern is always the same: minute → hour → day-of-month → month → day-of-week. Left to right. Once that order is locked in your head, no cron expression will ever feel like magic again — just straightforward scheduling logic you can read at a glance.
Now go update those * * * * * jobs in your configs. I know at least one of them is running way more often than you actually need it to.