Skip to content
Free Tool Arena

How-To & Life · Guide · Developer Utilities

How to read cron expressions

The five fields, wildcards, lists, ranges, steps, @hourly/@daily shortcuts, Quartz-style second field, and translating expressions to English.

Updated April 2026 · 6 min read

Cron expressions look like five or six inscrutable tokens separated by spaces, and most engineers learn them by copy-pasting examples until something breaks. That’s fine until the schedule fires at the wrong hour, skips a daylight-saving transition, or runs twice on the last day of the month because day-of-month and day-of-week were both set. Understanding what each field means, what the special characters do, and how classic cron differs from the Quartz and Kubernetes variants turns cron from a guessing game into a precise scheduling language. This guide covers the five-field and six-field formats, the meaning of *, /, ,, and -, the shortcuts like @daily, the day-of-month vs. day-of-week trap, time zone handling, and how to read an expression out loud in English.

Advertisement

The five fields

Classic Unix cron has five fields, left to right: minute, hour, day of month, month, day of week. Each field has a fixed value range.

┌───────────── minute        (0 - 59)
│ ┌─────────── hour          (0 - 23)
│ │ ┌───────── day of month  (1 - 31)
│ │ │ ┌─────── month         (1 - 12) or JAN-DEC
│ │ │ │ ┌───── day of week   (0 - 6)  or SUN-SAT
│ │ │ │ │
* * * * *   command to execute

Each field defaults to “every” (*). An expression like * * * * * runs every minute forever. 0 * * * * runs at minute zero of every hour. 0 0 * * * runs at midnight every day.

The six-field variant (Quartz)

Java’s Quartz scheduler and some cloud schedulers add a seconds field at the front and sometimes a year field at the end. So 0 0 0 * * * in Quartz runs daily at midnight, not every minute. Kubernetes CronJobs use classic five-field. Know which variant your system speaks before pasting examples.

5-field (Unix):   minute hour dom month dow
6-field (Quartz): second minute hour dom month dow
7-field (Quartz): second minute hour dom month dow year

Special characters

* means “every value in this field.” , separates a list of specific values. - denotes an inclusive range. / means step, with a/b being “starting at a, every b.” These compose: */15 in the minute field means every 15 minutes (0, 15, 30, 45). 0,30 means at 0 and 30 only. 9-17 means every value from 9 through 17 inclusive.

*/15 * * * *      every 15 minutes
0 9-17 * * 1-5    on the hour, 9 AM to 5 PM, Mon-Fri
0 0 1,15 * *      at midnight on the 1st and 15th
30 6-22/2 * * *   at :30, every 2 hours from 6 AM to 10 PM

Shortcut strings

Most cron implementations accept named shortcuts. @yearly (or@annually) = 0 0 1 1 *, midnight January 1. @monthly = 0 0 1 * *. @weekly = 0 0 * * 0, Sunday midnight. @daily (or @midnight) = 0 0 * * *.@hourly = 0 * * * *. @reboot runs once at system start (not supported everywhere).

Day of month vs. day of week

Classic Unix cron treats these two fields with an OR relationship. If both are restricted, the job runs whenever either matches. This trips people up constantly.

0 0 15 * 1   runs at midnight on the 15th OR every Monday
             NOT "midnight on the 15th only if it's Monday"

If you want AND (only on the 15th when it's Monday), you must
check the day of week inside your script.

Quartz reverses this: one of the two fields must be ? (meaning “no specific value”) and the other is the one used. Know your dialect.

Day of week numbering

Classic cron uses 0 = Sunday, 6 = Saturday, with 7 sometimes accepted as Sunday too. Quartz uses 1 = Sunday, 7 = Saturday. Some systems use 0 = Monday. Always verify against your scheduler’s documentation before writing * * * * 1 and discovering it ran on a Tuesday.

Time zones

Classic cron runs in the system time zone. If your server is UTC but you want the job to fire at 9 AM Eastern, you have to convert and deal with daylight saving time. Modern schedulers (Kubernetes CronJob spec.timeZone, AWS EventBridge, GitHub Actions, Vercel Cron) let you specify a time zone explicitly, which handles DST automatically. For critical schedules, either use UTC and accept a shifting local time, or use a scheduler that supports named zones.

Reading an expression aloud

Walk left to right and describe each field. */5 9-17 * * 1-5 reads as “every 5 minutes, from 9 AM through 5 PM, every day of the month, every month, Monday through Friday.” Collapse “every” clauses that don’t add information: “every 5 minutes between 9 AM and 5 PM, Monday through Friday.”

Common real-world patterns

0 0 * * *          daily at midnight
0 */6 * * *        every 6 hours
*/5 * * * *        every 5 minutes
0 9 * * 1-5        weekdays at 9 AM
0 0 1 * *          first of every month at midnight
0 0 1 1 *          New Year's at midnight
15 14 1 * *        2:15 PM on the 1st of every month
0 22 * * 5         Fridays at 10 PM
0 0 * * 0          Sunday midnight (weekly cleanup)

Special extensions

Some cron implementations extend the syntax. L means “last” (last day of month, last Friday). W means “weekday nearest to.”# picks a specific occurrence in a month (5#3 = third Friday). These are Quartz extensions and may not work in your scheduler.

Common mistakes

Treating day-of-month and day-of-week as AND. In classic cron, setting both means OR. 0 0 15 * 1 runs on the 15th and every Monday, not “Monday the 15th only.”

Using the wrong scheduler dialect. Quartz has six fields by default, classic cron has five. 0 0 0 * * * means different things in each.

Ignoring time zones. If your production server is UTC and your Monday 9 AM report fires at 4 AM local time, you forgot to adjust. DST transitions will also shift schedules unless your runtime handles zones.

Assuming */7 fires every 7 minutes. */7 in the minute field fires at 0, 7, 14, 21, 28, 35, 42, 49, 56, then jumps back to 0. The gap between :56 and :00 is only 4 minutes. Step notation restarts each hour.

Running a long-lived job every minute and overlapping. Cron doesn’t skip overlapping runs. If your 2-minute job is scheduled every minute, you’ll have concurrent instances piling up. Add a lockfile or use a scheduler that enforces non-overlap.

Forgetting @reboot isn’t universal. Kubernetes CronJobs, many cloud schedulers, and non-Unix systems don’t support it. Use a systemd unit or startup script instead.

Writing 0 0 31 2 * expecting monthly. February never has 31 days, so this runs never. Cron doesn’t warn you; the job just silently never fires.

Run the numbers

Paste any cron expression into our cron expression explainer to see the next run times and a plain-English description. Pair it with the cron builder when you’re writing a new schedule, and the time zone converter to sanity- check what your UTC cron schedule means in local time.

Advertisement

Found this useful?Email