Cron expression for every 2 hours
The cron expression 0 */2 * * * runs a job every 2 hours on the hour — at 00:00, 02:00, 04:00, 06:00, 08:00, 10:00, 12:00, 14:00, 16:00, 18:00, 20:00 and 22:00 (12 firings per day). The */2 is step syntax: “every 2 units starting from 0”. The step works in any cron field; */2 in the day-of-month field would mean “every other day”, */2 in the minute field means “every other minute”.
Quick reference
| Platform | Expression |
|---|---|
| Unix / Linux crontab | 0 */2 * * * |
| Kubernetes CronJob | 0 */2 * * * |
| GitHub Actions | 0 */2 * * * |
| AWS EventBridge | cron(0 0/2 * * ? *) |
| Quartz (Java) | 0 0 0/2 ? * * |
How does step syntax (*/N) work?
*/N reads as “every N units starting from the lowest legal value of this field”. In the minute field (range 0–59), */15 hits 0, 15, 30, 45. In the hour field (range 0–23), */2 hits 0, 2, 4, …, 22. In the day-of-month field (range 1–31), */5 hits 1, 6, 11, 16, 21, 26, 31.
The “starting from 0” matters when N is not a divisor of the field’s range. For divisors of 24 — */2, */3, */4, */6, */8, */12 — the firings are evenly spaced across the day. For non-divisors — */5, */7, */9, */11 — there’s a gap at the day boundary because the count restarts at hour 0.
For example, */5 * * * in the hour field fires at 0, 5, 10, 15, 20. Then the day rolls over and the next firing is at hour 0 of the next day — a 4-hour gap, not 5.
If you want truly evenly-spaced “every N hours” and N is not a divisor of 24, you can’t get it with cron alone. Use a long-running scheduler that tracks elapsed time, or accept the day-boundary gap.
Variations
| Schedule | Expression |
|---|---|
| Every 2 hours on the hour | 0 */2 * * * |
| Every 2 hours starting at 1 AM (1, 3, 5, 7, …) | 0 1-23/2 * * * |
| Every 2 hours during business hours (9 AM – 5 PM) | 0 9-17/2 * * * |
| Every 3 hours | 0 */3 * * * |
| Every 4 hours | 0 */4 * * * |
| Every 6 hours | 0 */6 * * * |
| Every 12 hours (twice daily) | 0 */12 * * * (= 0 0,12 * * *) |
| Every 2 hours in AWS | cron(0 0/2 * * ? *) |
How do I use it on each platform?
Linux crontab:
0 */2 * * * /usr/local/bin/health-check
Kubernetes CronJob:
apiVersion: batch/v1
kind: CronJob
metadata:
name: health-check
spec:
schedule: "0 */2 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: check
image: my-org/health-check:1.0
restartPolicy: OnFailure
AWS EventBridge:
aws events put-rule \
--name health-check \
--schedule-expression "cron(0 0/2 * * ? *)"
EventBridge’s rate(2 hours) is an alternative that fires every 2 hours from rule-creation time — no clock alignment. Use cron() for clock-aligned 00:00, 02:00, 04:00; use rate() if you don’t care about the wall-clock minute.
GitHub Actions:
on:
schedule:
- cron: '0 */2 * * *'
Common mistakes
Expecting */5 in the hour field to be evenly spaced across the day boundary. It hits 0, 5, 10, 15, 20 — then resets to 0 the next day, leaving a 4-hour gap. If you need “every 5 hours from when the job was created”, use a rate() expression or a long-running scheduler.
Putting the step in the wrong field. */2 * * * * (step in MINUTE field) means every 2 minutes — 720 runs per day. 0 */2 * * * is what you want for every 2 hours. Easy mis-paste.
Using 0 */2 * * ? * in AWS without 0/2. Both work in modern AWS, but the older docs say only 0/2 is valid. For maximum compatibility with old IaC validators and copy-paste guides, prefer 0/2.
For other schedules see common cron schedules, or build a custom expression with the Cron Expression Builder.
Frequently asked questions
- How does the step syntax `*/2` work?
- `*/N` means "every N units, starting from the lowest legal value". In the hour field, `*/2` fires at 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22 — every other hour. In the minute field `*/15` fires at 0, 15, 30, 45. The step starts from the field minimum, not from when cron started.
- Why doesn''t `0 */5 * * *` run "every 5 hours"?
- It does run every 5 hours in a sense, but the result might surprise you. `*/5` in the hour field hits 0, 5, 10, 15, 20 — five firings per day, with a 4-hour gap between hour 20 and hour 0 of the next day (because the day rolls over and the step resets). For divisors of 24 (`*/2`, `*/3`, `*/4`, `*/6`, `*/8`, `*/12`), the gap is uniform. For non-divisors (`*/5`, `*/7`, `*/9`, `*/11`), the schedule is uneven across the day boundary.
- What''s the difference between `*/2` and `0/2` in the hour field?
- `*/2` and `0/2` are equivalent in standard Unix cron — both mean "every 2 starting from 0". AWS EventBridge prefers `0/2` syntactically; older AWS docs claim `*/2` is invalid (it isn't — it works), but `0/2` is the safer form for AWS-targeted IaC. To start at a different value, use the explicit form: `5/2` means 5, 7, 9, 11, 13, 15, 17, 19, 21, 23.
Need a different schedule?
Build cron expressions for Unix, Kubernetes and AWS — with a human-readable description and the next 5 run times.
Open the Cron Expression Builder →Related
Cron expression for every 5 minutes
The cron expression `*/5 * * * *` runs every 5 minutes in Linux crontab, Kubernetes, GitHub Actions; `cron(*/5 * * * ? *)` in AWS EventBridge.
Cron expression for every 15 minutes
The cron expression `*/15 * * * *` runs every 15 minutes — at :00, :15, :30, :45 — across Linux, Kubernetes, GitHub Actions and AWS.
Cron expression for every hour
The cron expression `0 * * * *` runs once per hour on the hour. Same syntax in Linux, Kubernetes and GitHub Actions; `cron(0 * * * ? *)` in AWS EventBridge.
Cron expression for daily at midnight
The cron expression `0 0 * * *` runs once daily at midnight. Watch the timezone — Kubernetes < 1.25 and AWS EventBridge default to UTC, not local time.
Cron expression for every weekday
The cron expression `0 9 * * 1-5` runs at 9 AM Monday through Friday. AWS uses `cron(0 9 ? * MON-FRI *)` because day-of-week numbering differs.
Cron expression for the first of every month
The cron expression `0 0 1 * *` runs at midnight on the 1st of every month. AWS uses `cron(0 0 1 * ? *)`.