Skip to main content
Tools Harbor

Cron expression for every 3 hours

The cron expression 0 */3 * * * runs a job every 3 hours on the hour — at 00:00, 03:00, 06:00, 09:00, 12:00, 15:00, 18:00 and 21:00. Eight firings per day, evenly spaced, with no day-boundary gap. The same syntax works in Linux crontab, Kubernetes CronJob and GitHub Actions. AWS EventBridge needs the 6-field form: cron(0 */3 * * ? *).

Quick reference

PlatformExpression
Unix / Linux crontab0 */3 * * *
Kubernetes CronJob0 */3 * * *
GitHub Actions0 */3 * * *
AWS EventBridge Rules / Schedulercron(0 */3 * * ? *)
Quartz (Java)0 0 */3 ? * *

Why */3 is “well-behaved” but */5 is not

Cron’s step syntax (*/N) works on every field but has a subtle gotcha: the step counts from the field’s minimum value, and the count restarts each time the next-larger field rolls over. For the hour field, that minimum is 0 and the rollover is the day boundary.

When N is a divisor of 24 — that is, 1, 2, 3, 4, 6, 8 or 12 — the firings stay evenly spaced across midnight. */3 fires at 00, 03, 06, 09, 12, 15, 18, 21 and then rolls into 00 of the next day. The gap from 21:00 to 00:00 is exactly 3 hours, same as every other gap.

When N is NOT a divisor of 24 — like 5, 7, 9, 10, 11 — the day-boundary gap is different from the in-day gap. */5 in the hour field fires at 00, 05, 10, 15, 20, then resets — a 4-hour gap from 20:00 to 00:00. If you genuinely need “every N hours” with even spacing for non-divisor N, cron alone can’t do it; use a long-running scheduler that tracks elapsed time, or an AWS rate(N hours) expression that fires N hours after the previous run regardless of clock time.

So */3 is one of the “safe” step values: pick it whenever you want exactly 8 evenly-spaced runs per day.

Variations

ScheduleExpression
Every 3 hours, on the hour0 */3 * * *
Every 3 hours, weekdays only0 */3 * * 1-5
Every 3 hours during business hours (9, 12, 15)0 9-17/3 * * *
Every 3 hours offset by 1 hour (1, 4, 7, 10, 13, 16, 19, 22)0 1-23/3 * * *
Every 3 hours at 30 past the hour (00:30, 03:30, …)30 */3 * * *
Every 3 hours, hourly between :00 and :02 (3 firings per slot)0-2 */3 * * *
Every 3 hours in AWScron(0 */3 * * ? *)

How do I use it on each platform?

Linux crontab:

0 */3 * * * /usr/local/bin/warm-cache

Kubernetes CronJob:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: warm-cache
spec:
  schedule: "0 */3 * * *"
  startingDeadlineSeconds: 600       # tolerate 10 min of scheduler lag
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: warm
              image: my-org/cache-warmer:1.0
          restartPolicy: OnFailure

A 3-hour interval is short enough that occasional scheduler skips matter. startingDeadlineSeconds: 600 lets the controller catch up if the kube-controller-manager was paused or the API server was unreachable for under 10 minutes — without it, a missed slot is silently skipped until the next 3-hour mark, leaving a 6-hour gap.

AWS EventBridge (CLI):

aws events put-rule \
  --name warm-cache \
  --schedule-expression "cron(0 */3 * * ? *)"

GitHub Actions:

on:
  schedule:
    - cron: '0 */3 * * *'

GitHub Actions cron is best-effort — high-load periods can delay a scheduled run by 5–15 minutes. For an “every 3 hours” workflow that needs tight alignment, treat the actual fire time as approximate and don’t chain other jobs to its exact start.

Common mistakes

Confusing */3 in the hour field with */3 in the minute field. */3 * * * * (step in MINUTE field) means every 3 minutes — 480 runs per day, two orders of magnitude more than intended. Always pin the minute to a single value (0) when stepping the hour field.

Expecting 9-17/3 to mean “every 3 hours that falls between 9 and 17”. It actually means “starting from 9, step by 3, until 17”: 9, 12, 15 — three firings, not five. The step is anchored to the low end of the range, not interpreted as a filter on top of */3.

Forgetting AWS’s 6-field requirement. cron(0 */3 * * *) is rejected by EventBridge — the 5-field form has no AWS variant. AWS needs cron(0 */3 * * ? *) with ? for day-of-week and a trailing * for year.

Daylight Saving transitions. On the spring-forward day, the 03:00 firing is skipped (the clock jumps from 02:59 to 04:00); on fall-back, the 02:00 firing happens twice. For schedules that must run exactly 8 times per day every day of the year, set the timezone to UTC in the CronJob timeZone field (Kubernetes 1.25+) or in EventBridge’s ScheduleExpressionTimezone.

For other intervals see common cron schedules, or build a custom expression with the Cron Expression Builder.

Frequently asked questions

Does `0 */3 * * *` produce evenly-spaced firings across midnight?
Yes — and this is the key reason to pick `*/3` over `*/5` or `*/7`. Because 3 is a divisor of 24, the schedule fires at 00:00, 03:00, 06:00, 09:00, 12:00, 15:00, 18:00, 21:00 — eight firings, exactly 3 hours apart, with no awkward gap when the day rolls over. `*/5` in the hour field, by contrast, fires at 00, 05, 10, 15, 20 and then resets to 00 — a 4-hour gap at the day boundary, not 5.
How is "every 3 hours" different from "every 4 hours" in practice?
Eight firings per day vs six. The 3-hour cadence sits in the sweet spot for cache warming, light health checks and metric rollups — frequent enough that stale data is a non-issue, infrequent enough that per-run cost stays bounded. Every 4 hours starts to feel sparse for monitoring; every 2 hours starts to feel wasteful for batch work. Pick 3 when the alternatives feel either too tight or too loose.
Can I run every 3 hours during business hours only?
Yes — `0 9-17/3 * * *` fires at 09:00, 12:00 and 15:00 (three firings between 9 AM and 5 PM). Add weekday filtering with `0 9-17/3 * * 1-5`. Note that the step starts from the *low end of the range*, not from the field minimum — `9-17/3` means "9, 9+3, 9+6" not "every 3 hours that happens to fall in 9-17".

Need a different schedule?

Build cron expressions for Unix, Kubernetes, AWS EventBridge and Quartz — with a human-readable description and the next 5 run times.

Open the Cron Expression Builder →

Related