The Shack Developer Tips How to Read and Write Cron Job Expressions (With Examples)

How to Read and Write Cron Job Expressions (With Examples)

Back to All Posts

Cron is the Unix job scheduler that has been quietly running scripts, sending reports, rotating logs, and triggering backups on servers around the world since 1975. If you've worked with Linux, cloud hosting, CI/CD pipelines, GitHub Actions, or almost any web hosting control panel, you've encountered cron, or you will soon.

The syntax looks intimidating at first glance: 0 3 * * 1 doesn't exactly announce itself as "every Monday at 3 AM." But cron expressions follow a simple, consistent structure, and once it clicks, you can read and write any schedule in seconds. This guide covers everything you need: the five fields, every special character, the most common real-world schedules, and the mistakes that catch people out.

The Five Fields

A standard cron expression has five space-separated fields, each representing a unit of time. From left to right:

┌───────────── minute        (0–59)
│ ┌─────────── hour          (0–23)
│ │ ┌───────── day of month  (1–31)
│ │ │ ┌─────── month         (1–12)
│ │ │ │ ┌───── day of week   (0–6, Sunday = 0)
│ │ │ │ │
* * * * *  command to execute

An asterisk (*) in any position means "every valid value for this field." So * * * * * runs every minute of every hour of every day, the cron equivalent of an infinite loop. Everything else is a variation on restricting or combining values within those five fields.

Special Characters

Cron uses four special characters that let you express almost any schedule without needing multiple entries.

Asterisk * — Every value

Matches every possible value for the field. * * * * * runs every minute. 0 * * * * runs at the top of every hour.

Comma , — List of values

Lets you specify multiple discrete values. 0 9,17 * * * runs at 9:00 AM and 5:00 PM every day. * * * * 1,3,5 runs every minute on Monday, Wednesday, and Friday.

Hyphen - — Range of values

Defines a continuous range. 0 9-17 * * * runs at the top of every hour between 9 AM and 5 PM. * * * * 1-5 runs every minute on weekdays only.

Slash / — Step values

Runs at regular intervals. */15 * * * * runs every 15 minutes. 0 */6 * * * runs every 6 hours at the top of the hour (midnight, 6 AM, noon, 6 PM). You can also use steps on ranges: 0 8-18/2 * * * runs every two hours between 8 AM and 6 PM.

Common Real-World Cron Expressions

These cover the vast majority of scheduled tasks you'll encounter or need to write:

# Every minute
* * * * *

# Every 5 minutes
*/5 * * * *

# Every 15 minutes
*/15 * * * *

# Every hour (at :00)
0 * * * *

# Every day at midnight
0 0 * * *

# Every day at 3:30 AM
30 3 * * *

# Every Monday at 9 AM
0 9 * * 1

# Every weekday (Mon–Fri) at 8 AM
0 8 * * 1-5

# Every Sunday at midnight
0 0 * * 0

# First day of every month at midnight
0 0 1 * *

# Every quarter (Jan, Apr, Jul, Oct) on the 1st at midnight
0 0 1 1,4,7,10 *

# Once a year — January 1st at midnight
0 0 1 1 *
Build expressions visually: The DevToolShack Cron Expression Builder lets you construct any schedule with dropdowns and plain-English descriptions, then copies the finished expression for you. No memorisation required.

Named Shortcuts

Many cron implementations (including Vixie cron, the most common Linux variant) support named shortcuts as aliases for common expressions. These are worth knowing because they're more readable and less error-prone than typing the fields by hand:

@yearly   →  0 0 1 1 *       (once a year, January 1st at midnight)
@monthly  →  0 0 1 * *       (first day of every month at midnight)
@weekly   →  0 0 * * 0       (every Sunday at midnight)
@daily    →  0 0 * * *       (every day at midnight)
@hourly   →  0 * * * *       (every hour at :00)
@reboot   →  (run once at system startup)

Support varies by platform. @reboot in particular isn't universal, so check your environment's documentation before relying on them in production.

Day of Month vs. Day of Week

This is the most common source of confusion in cron scheduling. When you specify both a day-of-month value and a day-of-week value (i.e. neither is *), most cron implementations treat it as an OR condition, not AND.

For example: 0 0 15 * 1 does not mean "the 15th of the month, but only if it's a Monday." It means "run on the 15th of every month OR every Monday, whichever comes first." If you actually want "the 15th only when it falls on a Monday," you need to handle that logic inside the script itself.

Timezone Gotchas

Standard cron runs in the server's local timezone, which is often UTC on cloud servers, even when that's not what you expect. A job set to run at 0 9 * * * on a server in UTC will fire at 9 AM UTC, which is 4 AM or 5 AM US Eastern depending on daylight saving time.

A few things to keep in mind:

  • Check your server's timezone with timedatectl or cat /etc/timezone before writing time-sensitive schedules.
  • Daylight saving time can cause jobs to run an hour early or late, or to skip or double-fire around the transition. Jobs that run at 2 AM are particularly vulnerable.
  • Some cron variants (and most cloud schedulers like AWS EventBridge and GitHub Actions) let you set a timezone explicitly. Use it when it's available.
  • Logging the actual execution time inside your script is good practice and makes DST-related surprises obvious.

Cron in Modern Environments

Traditional cron (crontab -e on a Linux server) is still widely used, but cron expression syntax has been adopted broadly across modern tooling:

  • GitHub Actions: The schedule trigger uses standard 5-field cron syntax (always UTC). Example: - cron: '0 6 * * 1' runs every Monday at 6 AM UTC.
  • AWS EventBridge / CloudWatch Events: Uses a 6-field variant with an added year field, and uses ? instead of * for "no specific value" in day fields.
  • Kubernetes CronJobs: Standard 5-field syntax, with an optional @` shorthand support depending on the controller.
  • cPanel / Plesk / DirectAdmin: GUI-based cron editors that generate standard cron expressions. It's useful to know the underlying syntax when the GUI doesn't cover your exact use case.
  • Laravel, Django, Node (node-cron): Application-level schedulers that accept cron expressions directly in code, sometimes with a 6th field for seconds.
The seconds field: Standard Unix cron has no seconds field; the smallest unit is one minute. Some application-level schedulers (node-cron, Spring Scheduler, Quartz) add a 6th field at the front for seconds. If you paste a 6-field expression into a standard crontab, it will either error or misinterpret the fields. Always check which variant your environment expects.

Common Mistakes to Avoid

Forgetting that day-of-week 0 and 7 are both Sunday. Most implementations accept 0–7 for day-of-week, where both 0 and 7 mean Sunday. Using 7 is technically valid but can cause confusion when reading expressions.

Using 0 0 * * 7 thinking it means Saturday. Saturday is 6, not 7. * * * * 7 is Sunday again.

Assuming */60 means "every 60 minutes." It doesn't. 60 is out of range for the minutes field (0–59), and behavior is undefined or an error depending on implementation. Use 0 * * * * for hourly.

Scheduling too many jobs at the same time. A common anti-pattern is starting all cron jobs at 0 * * * * (top of the hour) or 0 0 * * * (midnight). If you have ten jobs all starting simultaneously, stagger them, such as 5 0 * * *, 10 0 * * *, etc., to avoid resource spikes.

No output logging. By default, cron emails output to the local system user (which usually goes nowhere). Redirect stdout and stderr to a log file: 0 3 * * * /path/to/script.sh >> /var/log/myjob.log 2>&1


Not sure if your expression is right? Paste it into the Cron Expression Builder and it shows a plain-English description of exactly when the job will run and lists the next several scheduled execution times so you can verify before deploying.

Cron is one of those tools that rewards a small upfront investment in understanding. The five-field structure is consistent everywhere it appears, and the special characters cover every real-world scheduling pattern you're likely to need. Get comfortable with it once and it becomes one of the fastest, most reliable parts of any automation stack.