Cron Syntax Without the Pain: Scheduling Jobs That Run When You Expect

Cron has been the standard Unix job scheduler since 1975 and the syntax has barely changed. Five fields, a handful of special characters, and a straightforward logic — yet the number of people who schedule a job for 3 PM and find it ran at 3 AM (or not at all) is remarkably high. Most of those problems come from two sources: misreading the field order, and ignoring timezone.

The five fields

A cron expression has five space-separated fields:

┌─────────────── 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, 0=Sunday)
│ │ │ │ │
* * * * *

* * * * * runs every minute. That's the full-wildcard expression — every minute, every hour, every day.

The most common mistake: confusing minute and hour. 3 * * * * runs at minute 3 of every hour (e.g., 00:03, 01:03, 02:03...), not at 3 AM. To run at 3 AM daily: 0 3 * * *.

Special characters

* — wildcard. Matches any value. * * * * * fires every minute.

, — list. Multiple values. 0 9,17 * * * fires at 9:00 AM and 5:00 PM.

- — range. 0 9-17 * * * fires every hour from 9 AM to 5 PM (inclusive).

/ — step. */15 * * * * fires every 15 minutes. 0 */2 * * * fires every 2 hours on the hour.

? — no specific value. Used in some cron implementations (Quartz, GitHub Actions) where day-of-month and day-of-week can conflict. Not supported in standard Unix cron.

Common schedules and their expressions

# Every minute
* * * * *

# Every hour on the hour
0 * * * *

# Every day at midnight
0 0 * * *

# Every day at 3 AM
0 3 * * *

# Every weekday at 9 AM
0 9 * * 1-5

# Every Monday at 8 AM
0 8 * * 1

# Every 15 minutes
*/15 * * * *

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

# Every day at noon and midnight
0 0,12 * * *

# Every Sunday at 2 AM
0 2 * * 0

Nickname shortcuts

Most cron implementations support common shortcuts:

  • @yearly or @annually0 0 1 1 * — Runs once a year at midnight on January 1.
  • @monthly0 0 1 * * — Runs once a month at midnight on the first day.
  • @weekly0 0 * * 0 — Runs once a week at midnight on Sunday.
  • @daily or @midnight0 0 * * * — Runs once a day at midnight.
  • @hourly0 * * * * — Runs once an hour at the beginning of the hour.
  • @reboot — Runs once at system startup.

The timezone problem

Standard Unix cron runs in the system timezone of the server executing it. If your server is in UTC (as most cloud servers are by default) and you want a job to run at 9 AM in Berlin (CET = UTC+1, CEST = UTC+2 in summer), you need to account for the offset.

In winter: 0 8 * * * runs at 9 AM CET (UTC+1).
In summer: 0 7 * * * runs at 9 AM CEST (UTC+2).

This means a "9 AM every weekday" job needs two different cron entries — or a system that understands named timezones. Some cron implementations (Vixie cron on newer Debian/Ubuntu, systemd timers) support a CRON_TZ or TZ variable. Cloud schedulers (AWS EventBridge, GCP Cloud Scheduler) typically let you specify a timezone in the scheduler configuration. Always check which approach your environment supports.

The safest default: schedule everything in UTC on UTC servers, and handle any timezone conversion in the script being called rather than in the cron expression itself. If you're working with Unix timestamps in those scripts, storing and comparing times in epoch seconds sidesteps most timezone conversion bugs entirely.

Day-of-month and day-of-week interaction

In standard Unix cron, if you specify both day-of-month and day-of-week (neither is *), the job runs when either condition is true, not both. 0 0 1 * 1 runs on the first of every month AND every Monday — not just on Mondays that fall on the first.

If you want "first Monday of every month," cron can't express that directly. You need to use a wildcard for day-of-month (*), set day-of-week to Monday, and then check in the script itself whether today is the first Monday.

Testing before deploying

The most important thing you can do with a cron expression: verify that it fires when you think it does before putting it in production. The Cron Expression Parser translates an expression to plain English and shows the next five scheduled run times — which makes it immediately obvious if you've confused the minute and hour fields, or if the expression won't fire as often as you expect.

Cron Expression Parser — Translate any cron expression to plain English and preview the next 5 scheduled run times instantly.

Open Tool