A cron expression is five fields separated by spaces that tell a scheduler when to run a job. Once you can read the fields, every schedule from "every 5 minutes" to "9 AM on weekdays" becomes easy to write. This guide covers the syntax, the four special characters, and the mistakes that make jobs run at the wrong time.
The five fields
┌───────────── minute (0–59)
│ ┌─────────── hour (0–23)
│ │ ┌───────── day of month (1–31)
│ │ │ ┌─────── month (1–12)
│ │ │ │ ┌───── day of week (0–7, Sunday is 0 or 7)
│ │ │ │ │
* * * * * command-to-runA job runs whenever the current time matches all of the fields, with one exception covered below. So 30 2 * * * means "minute 30 of hour 2, any day, any month, any weekday", or 2:30 AM every day.
The special characters
| Character | Meaning | Example | Reads as |
|---|---|---|---|
* | Any value | * * * * * | Every minute |
, | A list | 0 9,17 * * * | At 9:00 and 17:00 |
- | A range | 0 9 * * 1-5 | At 9:00, Monday to Friday |
/ | A step | */15 * * * * | Every 15 minutes |
You can combine them. */5 9-17 * * 1-5 means every 5 minutes from 9:00 to 17:55 on weekdays.
Many schedulers also accept names like MON-FRI or JAN, and shortcuts like @daily or @hourly. Plain numbers work everywhere, so they're the safer choice.
If you're not sure what an expression does, paste it into the cron explainer to get a plain-English description before you ship it.
15 common schedules
Each link opens a page with the field-by-field breakdown and the next run times.
| Schedule | Expression |
|---|---|
| Every minute | * * * * * |
| Every 5 minutes | */5 * * * * |
| Every 15 minutes | */15 * * * * |
| Every 30 minutes | */30 * * * * |
| Every hour | 0 * * * * |
| Every 6 hours | 0 */6 * * * |
| Every day at midnight | 0 0 * * * |
| Every day at 2 AM | 0 2 * * * |
| Twice a day | 0 0,12 * * * |
| Every weekday at 9 AM | 0 9 * * 1-5 |
| Every Monday | 0 0 * * 1 |
| Every 5 minutes during business hours | */5 9-17 * * 1-5 |
| First day of every month | 0 0 1 * * |
| Every quarter | 0 0 1 1,4,7,10 * |
| Every year | 0 0 1 1 * |
There are more in the full list of cron expression examples.
Mistakes that bite
Forgetting the minute field
* 9 * * * doesn't mean "at 9 AM". It means every minute from 9:00 to 9:59, which is 60 runs. To run once at 9 AM, set the minute: 0 9 * * *.
Day of month and day of week together
When both the day-of-month and day-of-week fields are restricted, classic cron runs the job when either one matches, not both. 0 0 13 * 5 runs on every 13th and every Friday, not only on Friday the 13th. If you need both conditions, schedule on one field and check the other inside the script.
Steps don't carry over between hours
*/7 * * * * runs at minutes 0, 7, 14 … 56, then at 0 again. The gap between :56 and :00 is 4 minutes, not 7. Steps restart at each hour (or day, for the hour field), so pick a step that divides evenly into 60 or 24 if you need even spacing.
Time zones
Cron uses the time zone of the machine or service running it, and on servers that's often UTC. GitHub Actions schedules are always in UTC. Kubernetes CronJobs use the controller's time zone unless you set spec.timeZone. A "9 AM" job written on a laptop in New York can easily run at 4 or 5 AM local time once it's deployed. The time zone converter helps you work out the UTC hour.
Using cron in common tools
crontab
Edit your user's crontab with crontab -e and add one line per job. Redirect output so failures leave a trace:
*/5 * * * * /usr/local/bin/sync.sh >> /var/log/sync.log 2>&1Cron runs with a minimal environment, so use absolute paths for commands and files.
Kubernetes CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-report
spec:
schedule: "0 2 * * *"
timeZone: "Etc/UTC"
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
containers:
- name: report
image: my-registry/report:latest
restartPolicy: OnFailureconcurrencyPolicy: Forbid stops a new run from starting while the previous one is still going.
GitHub Actions
on:
schedule:
- cron: '0 9 * * 1-5' # 09:00 UTC on weekdaysThe shortest interval GitHub allows is every 5 minutes, and scheduled runs can start late when GitHub is busy, so don't rely on exact timing.
Quick reference
- Five fields: minute, hour, day of month, month, day of week.
*any,,list,-range,/step.- Always set the minute field for "once at hour X" jobs.
- Check which time zone your scheduler uses before you deploy.
Writing a new schedule? Check it with the cron explainer first.