# Cron expression cheatsheet with the mistakes that bite

> A cron expression has five fields: minute, hour, day of month, month, day of week. Use */15 for intervals and comma lists for specific values. Never set both day-of-month and day-of-week - they combine with OR, not AND, which is the most common cron bug.

Source: https://rankcert.com/blog/cron-expression-cheatsheet
Published: 2026-08-29 · Updated: 2026-08-29

---


Five fields, in this order, separated by spaces:

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

Some systems add a sixth leading field for seconds. Quartz and Kubernetes CronJobs differ from standard Unix cron in small ways - check which dialect you are writing for before trusting any generator.

## Special characters

| Symbol | Meaning | Example |
|---|---|---|
| `*` | Every value | `* * * * *` - every minute |
| `,` | List | `0 9,17 * * *` - 9am and 5pm |
| `-` | Range | `0 9-17 * * *` - hourly, 9am to 5pm |
| `/` | Step | `*/15 * * * *` - every 15 minutes |
| `?` | No specific value | Quartz only, not standard cron |
| `L` | Last | `0 0 L * *` - last day of month, not universal |

## Expressions you will actually use

| Schedule | Expression |
|---|---|
| Every 5 minutes | `*/5 * * * *` |
| Every hour, on the hour | `0 * * * *` |
| Every day at 03:00 | `0 3 * * *` |
| Weekdays at 09:00 | `0 9 * * 1-5` |
| Every Monday at 09:00 | `0 9 * * 1` |
| First of the month at midnight | `0 0 1 * *` |
| Every 6 hours | `0 */6 * * *` |
| Twice a day, 08:00 and 20:00 | `0 8,20 * * *` |

## The mistakes that bite

**Day-of-month and day-of-week are OR, not AND.** `0 0 1 * 1` does not mean "the first of the month, if it is a Monday". It means "the first of the month, **or** any Monday". This is the single most common cron bug and it is in the POSIX spec, not a quirk.

**`*/7` does not mean every seven days.** Step values restart at the beginning of each field's range, not from the last run. `*/7` on day-of-month fires on the 1st, 8th, 15th, 22nd, 29th - then the 1st again, four days later.

**Timezones.** Most cron daemons run in the server's local timezone. If that observes daylight saving, a job scheduled at 02:30 runs twice on one day a year and not at all on another. Run schedulers in UTC and convert at the edges.

**Overlapping runs.** Cron does not check whether the previous run finished. A job scheduled every five minutes that takes seven will pile up until the box falls over. Take a lock at the start of the job and exit if you cannot get it.

**No retries and no alerting.** A failed cron job is silent by default. If a job matters, it needs to report success somewhere that alerts on absence - the failure mode you care about is the job that stopped running, and by definition it cannot tell you that itself.

<Callout>
Before deploying any expression, write out the next five fire times by hand. If your mental model and the schedule disagree, better to find out now than in a postmortem.
</Callout>

<Cta />
