Dev Tools

How to Read a Cron Expression Without Guessing in 2026

Cron syntax hasn't changed in decades, but it still trips up developers deploying scheduled jobs. Here's how to read any expression correctly and verify it before you deploy.

📅 Jul 29, 2026·⏱️ 5 min read·✍️ Cikal Studio Labs
⏱️

Why Cron Syntax Still Causes Production Incidents

Cron expressions have powered scheduled jobs since the 1970s, and the syntax hasn't meaningfully changed. That stability is also the problem: five terse fields separated by spaces, with no built-in validation in most systems, means a single typo can silently schedule a job for the wrong time — or never run it at all. "It'll run at 9am" and "it actually runs at 9am" are two different claims, and only one of them is verified by actually reading the fields correctly.

The Five Fields, In Order

A standard cron expression has exactly five space-separated fields, always in this order:

  • Minute (0-59)
  • Hour (0-23)
  • Day of month (1-31)
  • Month (1-12)
  • Day of week (0-7, where both 0 and 7 mean Sunday)

Each field independently supports an asterisk (any value), a specific number, a range like 1-5, a step like */15 (every 15 units), or a comma-separated list like 1,15,30. It's easy to misread which field is which — a common bug is writing * 9 * * 1-5 when you meant 0 9 * * 1-5, which is the difference between "every minute during the 9am hour on weekdays" and "at exactly 9:00am on weekdays".

The Day-of-Month / Day-of-Week Gotcha

The single most confusing rule in cron: when both the day-of-month and day-of-week fields are restricted (neither is *), most cron implementations treat them as an OR, not an AND. So 0 0 1 * 1 means "midnight on the 1st of the month, OR every Monday" — not "only when the 1st falls on a Monday", which is what many developers assume at first glance. This single behavior has caused more than a few scheduled jobs to run far more often than intended.

Common Schedules Worth Memorizing

  • * * * * * — every single minute
  • 0 * * * * — the top of every hour
  • 0 0 * * * — midnight every day
  • */15 * * * * — every 15 minutes
  • 0 9 * * 1-5 — 9am, Monday through Friday
  • 0 0 1 * * — midnight on the first day of every month

Verify Before You Deploy

  1. Read each field in order — minute, hour, day-of-month, month, day-of-week — out loud if it helps.
  2. Watch for the day-of-month/day-of-week OR trap whenever both fields are restricted simultaneously.
  3. Compute the actual next few run times against the real current date, not just a mental description — this catches off-by-one field mistakes instantly.
  4. Double-check step values like */15 — they always start counting from the field's minimum (0), not from whatever time you happen to deploy at.
  5. Test edge-of-month schedules carefully — a day-of-month value like 31 simply never fires in months with fewer days, which is valid but easy to forget.

A cron expression that looks right and a cron expression that runs right are only the same thing once you've actually computed its next few executions and confirmed they match your intent.

Testing Your Schedule Before Deploying to Production

Scheduled jobs fail quietly in a way that request-driven code doesn't. If an API endpoint has a bug, someone notices within minutes because a request fails visibly. If a cron job has a bug, it might simply not run — or run at the wrong time — for days or weeks before anyone checks the logs and realizes the nightly backup hasn't fired since a deploy three Tuesdays ago.

Before deploying any new or modified cron expression, compute its next several run times against the actual current date and eyeball them against your expectation. This single step catches an enormous share of scheduling bugs: a step value that starts counting from the wrong baseline, a day-of-week field using the wrong numbering convention (some systems use 1-7 for Monday-Sunday instead of 0-6 for Sunday-Saturday), or a month field accidentally left as a day-of-month value after a copy-paste edit.

It's also worth testing across a timezone-sensitive boundary if your infrastructure runs in UTC while your team thinks in local time. A job scheduled for 0 9 * * * on a UTC server runs at 9am UTC, not 9am in whatever timezone the person who wrote it was sitting in — a mismatch that has caused more than one "why did this run at 2am" incident. Confirming the next run time in the server's actual timezone, not just trusting the expression's face value, closes that gap before it becomes a page at an inconvenient hour.

Frequently Asked Questions

What do the five fields in a cron expression mean, in order?

In order, the five space-separated fields are minute (0-59), hour (0-23), day of month (1-31), month (1-12), and day of week (0-7, where both 0 and 7 mean Sunday). Each field independently accepts an asterisk for 'any value', a specific number, a range like 1-5, a step like */15, or a comma-separated list — and a common bug is writing * 9 * * 1-5 when 0 9 * * 1-5 was intended, which is the difference between running every minute during the 9am hour versus running exactly once at 9:00am.

Why does 0 0 1 * 1 run on both the 1st of the month and every Monday, instead of only when the 1st is a Monday?

This is the single most confusing rule in cron: when both the day-of-month and day-of-week fields are restricted at the same time (neither is *), most cron implementations treat the relationship as OR, not AND. So 0 0 1 * 1 actually means 'midnight on the 1st of the month, OR every Monday' — a behavior that has caused more than a few scheduled jobs to run far more often than the person who wrote them intended.

Why did my cron job start running at the wrong time after a deployment?

Scheduled jobs fail quietly compared to request-driven code — there's no visible error, so a wrong schedule can persist for days or weeks before anyone checks the logs. Common causes include a step value like */15 that starts counting from the field's minimum rather than your deploy time, a day-of-week field using a different numbering convention than you assumed (some systems use 1-7 for Monday-Sunday instead of 0-6 for Sunday-Saturday), or a UTC-vs-local-timezone mismatch where a job scheduled for 0 9 * * * runs at 9am UTC, not 9am in the timezone the person who wrote it was thinking in.

How can I verify exactly when a cron expression will run before deploying it to production?

Compute the next several actual run times against the real current date rather than trusting a mental description of the expression — this single step catches an enormous share of scheduling bugs, including off-by-one field mistakes and step values that don't start counting where you expect. It's also worth checking the result in your server's actual timezone if your infrastructure runs in UTC while your team thinks in local time, since that mismatch is a frequent cause of 'why did this run at 2am' incidents.

Is there a tool that shows me the next run times for a cron expression?

Yes — a Cron Expression Parser can take any five-field expression, break down what each field means in plain language, and compute the actual next run times against the current date, which is the fastest way to catch the day-of-month/day-of-week OR trap or a mistyped field before it reaches production.