Skip to content
ZeroServer.tools
All guides

Cron Expressions Explained: Syntax, Examples, and Tips

June 10, 2026 · 8 min read

Cron Expressions Explained: Syntax, Examples, and Tips

Cron is the time-based job scheduler found on virtually every Unix-like system. It's been running scheduled tasks since 1975, and despite decades of newer alternatives, cron and cron-inspired syntax remains the universal language for "run this at this time." Understanding cron expressions is a fundamental skill for any developer who works with servers, CI/CD, or cloud infrastructure.

Use the Crontab Generator to build expressions visually, and the Cron Parser to decode what an existing expression actually means.


The 5-Field Syntax

A standard cron expression has five fields separated by spaces:

┌───────── minute (0–59)
│ ┌───────── hour (0–23)
│ │ ┌───────── day of month (1–31)
│ │ │ ┌───────── month (1–12)
│ │ │ │ ┌───────── day of week (0–7, where 0 and 7 are both Sunday)
│ │ │ │ │
* * * * *

Every field must contain a valid value, a wildcard, or a special expression. The job runs whenever all five fields match the current time.

0 9 * * 1-5
│ │ │ │ └── Monday through Friday
│ │ │ └──── any month
│ │ └────── any day of month
│ └──────── 9 AM (09:00)
└────────── at minute 0

This runs at 9:00 AM every weekday.

Some environments (Quartz scheduler, AWS EventBridge, some crontab implementations) use a 6-field format that adds seconds as the first field, or a 7-field format that adds a year field. Standard Unix cron uses 5 fields.


Special Characters

* — Wildcard (Every)

Matches every valid value for that field.

* * * * *   → runs every minute of every hour of every day
0 * * * *   → runs at the start of every hour

, — List

Matches multiple specific values.

0 9,17 * * *     → 9:00 AM and 5:00 PM every day
0 0 1,15 * *     → midnight on the 1st and 15th of each month
0 0 * * 1,3,5    → midnight on Monday, Wednesday, Friday

- — Range

Matches a contiguous range of values.

0 9 * * 1-5      → 9:00 AM weekdays (Monday to Friday)
0 9-17 * * *     → every hour from 9 AM to 5 PM
*/1 8-20 * * *   → every minute from 8 AM to 8 PM

/ — Step

Matches every Nth value within a range. */5 means "every 5 units", 0/15 means "starting at 0, every 15 units".

*/15 * * * *    → every 15 minutes
0 */4 * * *     → every 4 hours (at minute 0: 00:00, 04:00, 08:00, ...)
*/5 9-17 * * *  → every 5 minutes between 9 AM and 5 PM

? — No Specific Value

Used in day-of-month or day-of-week fields when you want to specify one but not the other. Standard Unix cron doesn't use ?, but Quartz, Spring Scheduler, and AWS EventBridge do.

0 9 15 * ?    → 9 AM on the 15th, regardless of what day of the week it is
0 9 ? * 1     → 9 AM every Monday, regardless of the day of month

In standard Unix cron, use * where you'd use ? in Quartz.

L — Last

Used in Quartz and some extended cron implementations. L in day-of-month means the last day of the month. 5L in day-of-week means the last Friday of the month.

0 0 L * ?     → midnight on the last day of every month (Quartz)
0 0 ? * 5L    → midnight on the last Friday of every month (Quartz)

W — Nearest Weekday

W in day-of-month means the nearest weekday to that date. 15W means the nearest weekday to the 15th.

0 9 15W * ?   → 9 AM on the nearest weekday to the 15th (Quartz)

Common Examples

Run Every N Minutes

*/5 * * * *     → every 5 minutes
*/10 * * * *    → every 10 minutes
*/15 * * * *    → every 15 minutes
*/30 * * * *    → every 30 minutes

Run at Specific Times

0 9 * * *       → 9:00 AM every day
30 14 * * *     → 2:30 PM every day
0 0 * * *       → midnight every day
0 6,12,18 * * * → 6 AM, noon, and 6 PM every day

Run on Specific Days

0 9 * * 1-5     → 9 AM weekdays only
0 10 * * 6,0    → 10 AM on weekends (Saturday=6, Sunday=0)
0 9 * * 1       → 9 AM every Monday
0 0 * * 0       → midnight every Sunday

Run on Specific Dates

0 0 1 * *       → midnight on the 1st of every month
0 0 1 1 *       → midnight on January 1st (New Year)
0 0 1,15 * *    → midnight on the 1st and 15th of each month
30 4 1,15 * *   → 4:30 AM on the 1st and 15th

Run at Startup

This is not a time-based expression but a special cron keyword:

@reboot /path/to/script.sh    → run once when the system starts

Other special strings in many cron implementations:

@hourly    → 0 * * * *
@daily     → 0 0 * * *
@weekly    → 0 0 * * 0
@monthly   → 0 0 1 * *
@yearly    → 0 0 1 1 *

The Timezone Warning

This is the most common source of cron bugs in production. Standard cron runs in the server's local timezone. If your server is in UTC (as most cloud VMs are by default) and you write 0 9 * * *, that's 9 AM UTC — which is 4 AM Eastern, 1 AM Pacific, or 2 PM IST.

# Check your server's timezone
timedatectl
# or
date +%Z

For user-facing scheduled jobs, always either:

  1. Run your server in UTC and document that all cron times are UTC
  2. Use a scheduling system that accepts IANA timezone identifiers
  3. Mentally convert your target time to UTC before writing the expression

In systemd timers, you can specify timezone explicitly:

[Timer]
OnCalendar=Mon..Fri 09:00 America/New_York

GitHub Actions and most cloud schedulers also accept TZ settings or use UTC by default and document it clearly.


Where Cron Expressions Are Used

crontab -e

The standard way to edit cron jobs on Linux/macOS:

crontab -e          # edit current user's crontab
crontab -l          # list current user's crontab
sudo crontab -e     # edit root's crontab
crontab -u www-data -e  # edit another user's crontab

Each line in the crontab is a cron expression followed by the command to run:

*/30 * * * * /usr/bin/python3 /home/user/scripts/sync.py >> /var/log/sync.log 2>&1
0 2 * * * /usr/bin/pg_dump mydb > /backups/db-$(date +\%Y\%m\%d).sql

systemd Timers

Modern Linux systems increasingly use systemd timers as a cron replacement. They provide better logging, dependency handling, and timezone support:

# /etc/systemd/system/my-job.timer
[Unit]
Description=Run my-job daily

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target
systemctl enable my-job.timer
systemctl start my-job.timer
systemctl list-timers --all

GitHub Actions

GitHub Actions uses 5-field cron syntax in UTC:

on:
  schedule:
    - cron: '0 8 * * 1-5'   # 8 AM UTC, Monday to Friday
    - cron: '0 0 1 * *'      # midnight UTC on the 1st of each month

Note: GitHub Actions cron jobs can be delayed by up to a few minutes during high-traffic periods. They're not suitable for second-level precision.

Kubernetes CronJob

apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-report
spec:
  schedule: "0 2 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: report
            image: my-report-image

Kubernetes CronJob also supports a timeZone field in newer versions:

spec:
  timeZone: "America/New_York"
  schedule: "0 9 * * 1-5"

Cloud Schedulers

AWS EventBridge Scheduler, Google Cloud Scheduler, and Azure Logic Apps all support cron expressions (sometimes with 6-field Quartz syntax). Always check the documentation for whether they use standard 5-field or Quartz 6-field syntax.


Testing Cron Expressions Before Deploying

Never deploy a cron expression without verifying it first. Mistakes like 0 9 * * * vs 9 0 * * * (9 AM vs 12:09 AM) are easy to make and hard to notice.

The Cron Parser shows you the next N scheduled run times for any expression, which immediately reveals if the schedule is what you intended. The Crontab Generator lets you build expressions from natural-language inputs and see the schedule before committing it.

For command-line testing, crontab -e changes take effect immediately, but you can also use systemd-analyze calendar to test systemd OnCalendar expressions:

systemd-analyze calendar "Mon..Fri 09:00"
# Shows next trigger times and UTC equivalent

Quick Reference Card

Expression Meaning
* * * * * Every minute
0 * * * * Every hour
0 9 * * * 9 AM daily
0 9 * * 1-5 9 AM weekdays
*/15 * * * * Every 15 minutes
0 0 1 * * Midnight, 1st of month
0 0 * * 0 Midnight every Sunday
30 4 1,15 * * 4:30 AM on 1st and 15th
0 0 1 1 * Midnight Jan 1st (annually)
@daily Same as 0 0 * * *
@reboot Once at system startup

Use the Crontab Generator to build any schedule visually and export the expression, or the Cron Parser to decode an existing one into plain English with upcoming run times.