---
title: "Cron Syntax Cheatsheet"
slug: "cron-syntax"
category: "cheatsheets"
tags: ["cheatsheets", "cron", "scheduling", "linux", "ops", "cli"]
status: "stable"
last_updated: 2026-05-14
summary: "Cron field grammar, ranges, lists, step values, special strings, and the timezone and day-of-week traps that cause silent misfires."
related:
  [
    "[[cheatsheets/bash-one-liners]]",
    "[[ops/github-pages]]",
    "[[cheatsheets/ssh-config]]",
    "[[cheatsheets/git-commands]]",
    "[[ops/cloudflare]]",
    "[[cheatsheets/tmux-commands]]",
    "[[cheatsheets/index|Cheatsheets]]",
  ]
---

## Overview

Cron expressions describe recurring schedules as five (or six) space-separated fields. The same grammar appears in `/etc/crontab`, GitHub Actions `schedule:`, AWS EventBridge, Kubernetes CronJobs, and most task schedulers. This card covers the field grammar, special characters, common patterns, and the edge cases that cause schedules to run at the wrong time or not at all.

## Field positions

Standard cron uses five fields. Some systems add a seconds field at position 0 or a year field at position 5.

| Field | Position | Range | Notes |
|---|---|---|---|
| Minute | 1st | 0-59 | `0` = top of the hour |
| Hour | 2nd | 0-23 | 24-hour clock, UTC unless configured otherwise |
| Day of month | 3rd | 1-31 | `L` for last day (some engines) |
| Month | 4th | 1-12 or JAN-DEC | Month names are case-insensitive |
| Day of week | 5th | 0-7 or SUN-SAT | 0 and 7 both mean Sunday |
| Seconds | 0th (optional) | 0-59 | AWS, Spring, and Quartz add this before minute |
| Year | 6th (optional) | 1970-2099 | AWS EventBridge supports this |

GitHub Actions uses 5-field UTC cron: `on: schedule: - cron: "0 9 * * 1"` runs at 09:00 UTC every Monday.

## Special characters

| Character | Meaning | Example | Matches |
|---|---|---|---|
| `*` | Every value in field | `* * * * *` | Every minute |
| `,` | List separator | `0 9,17 * * *` | 09:00 and 17:00 |
| `-` | Range | `0 9-17 * * *` | Every hour 09:00 to 17:00 |
| `/` | Step | `*/15 * * * *` | Every 15 minutes |
| `/` with range | Step in range | `0 8-20/2 * * *` | Every 2 hours from 08:00 to 20:00 |
| `L` | Last (some engines) | `0 0 L * *` | Last day of the month at midnight |
| `W` | Nearest weekday (Quartz) | `0 0 15W * *` | Nearest weekday to the 15th |
| `#` | Nth weekday (Quartz) | `0 0 * * 5#2` | Second Friday of the month |
| `?` | No specific value (Quartz) | `0 0 ? * MON` | Required when day-of-month and day-of-week both set |

`L`, `W`, and `#` are extensions. Standard cron (`vixie-cron`, GitHub Actions) does not support them.

## Common schedule patterns

| Expression | Meaning |
|---|---|
| `* * * * *` | Every minute |
| `0 * * * *` | Every hour, on the hour |
| `0 0 * * *` | Daily at midnight UTC |
| `0 9 * * *` | Daily at 09:00 UTC |
| `0 9 * * 1` | Every Monday at 09:00 |
| `0 9 * * 1-5` | Weekdays at 09:00 |
| `0 9 * * 6,0` | Weekends at 09:00 |
| `0 0 1 * *` | First day of every month at midnight |
| `0 0 1 1 *` | January 1st at midnight (annual) |
| `*/5 * * * *` | Every 5 minutes |
| `*/15 9-17 * * 1-5` | Every 15 min during business hours on weekdays |
| `0 0,12 * * *` | Twice daily: midnight and noon |
| `@reboot` | At system startup (vixie-cron only) |
| `@hourly` | Same as `0 * * * *` |
| `@daily` | Same as `0 0 * * *` |
| `@weekly` | Same as `0 0 * * 0` |
| `@monthly` | Same as `0 0 1 * *` |

Use `https://crontab.guru` to verify expressions interactively.

## Timezone handling

Most cron daemons run in the system timezone or UTC. Timezone mismatches are the most common source of "wrong time" bugs.

| System | Timezone default | How to change |
|---|---|---|
| `vixie-cron` (Linux) | System timezone | `CRON_TZ=America/New_York` line in crontab |
| `crontab -e` | System timezone | `CRON_TZ=` or `TZ=` at top of file |
| GitHub Actions | UTC always | Convert local time to UTC before writing expression |
| AWS EventBridge | UTC | Set timezone in the EventBridge rule (separate field) |
| Kubernetes CronJob | UTC | Set `spec.timeZone: "America/New_York"` (k8s 1.27+) |

A job scheduled for `0 9 * * *` on a UTC system runs at 09:00 UTC, which is 04:00 EST in winter and 05:00 EDT in summer.

## Validation and testing

| Tool | Purpose |
|---|---|
| `crontab -l` | List current user's crontab |
| `crontab -e` | Edit current user's crontab |
| `/var/log/syslog` or `journalctl -u cron` | View cron execution log |
| `run-one` | Prevent overlapping runs of the same job |
| `flock -n /tmp/job.lock cmd` | Advisory lock to prevent overlap |
| `crontab.guru` | Web expression validator |

## Common gotchas

- Day-of-week and day-of-month are OR'd in vixie-cron: `0 0 1 * 1` fires on the 1st of the month OR every Monday, not the intersection. Use a script check if you need AND logic.
- The `*/n` step always starts at 0 (or 1 for DOM): `*/7` means 0, 7, 14, 21, 28, 35, 42, 49, 56, not evenly spaced seven-minute slots.
- Environment variables in crontabs are minimal. `PATH`, `HOME`, and `SHELL` are set; your login shell's `~/.bashrc` is not sourced. Use absolute paths for binaries.
- Output not redirected to a file is mailed to the local user via `sendmail`. On servers without a mail daemon, this fills `/var/spool/mail`. Add `>/dev/null 2>&1` to suppress.
- GitHub Actions `schedule` events are queued, not guaranteed. During high load, runs can be delayed by minutes or hours. Do not rely on sub-minute precision.

## Related

- [[cheatsheets/bash-one-liners]]
- [[ops/github-pages]]
- [[cheatsheets/ssh-config]]
- [[cheatsheets/git-commands]]
- [[ops/cloudflare]]
- [[cheatsheets/tmux-commands]]
- [[cheatsheets/index|Cheatsheets]]
