Cron expression for every 2 hours
The cron expression 0 */2 * * *
runs on the hour, every 2 hours. The restricted fields are minute 0 and hour */2; the remaining asterisks match anything. That works out to 12 runs per day.
0 */2 * * * Field breakdown
| Field | Value | Meaning |
|---|---|---|
| Minute | 0 | The top of the hour (:00) |
| Hour | */2 | Every 2 hours, counted from 0: 0, 2, 4, 6, 8, 10, … |
| Day of month | * | Every day of the month (1–31) |
| Month | * | Every month (1–12) |
| Day of week | * | Every day of the week (0–6) |
Next runs
- —
- —
- —
- —
- —
Run times are calculated in your browser timezone. The scheduler that executes the job uses its own — always UTC on Vercel Cron, UTC by default on GitHub Actions, the host clock on crontab.
How this schedule works
Cron evaluates the five fields of 0 */2 * * * — minute, hour, day of month, month, day of week — against the clock once a minute and starts the job on any minute where all five match, which cronstrue reads as "On the hour, every 2 hours": 12 runs per day. The runs land at 00:00, 02:00, 04:00, 06:00, 08:00, 10:00, 12:00, 14:00, 16:00, 18:00, 20:00, 22:00. Twenty-four divides by 2 without a remainder, so the spacing survives midnight: the gap from the 22:00 run to the next day's 00:00 run is the same 2 hours as every other gap.
When to use every 2 hours
Two-hourly runs fit medium-weight ETL, regenerating a static feed or sitemap, and checking an upstream source that only changes a few times a day. Twelve runs give each invocation a wide window, so a job that occasionally takes twenty minutes is in no danger of colliding with the next. Pick this cadence when hourly is more often than the data actually changes and the extra runs are pure cost. The runs land on even hours, which is worth knowing if you also keep a nightly maintenance window at an odd hour.
Common mistakes with every 2 hours
The expected mistake is 0 2 * * *, which is not "every two hours" but "once a day at 02:00" — a twelvefold reduction in runs that looks like a single missing character. The subtler one is expecting the step to be relative. It counts from hour 0, so the runs are locked to even hours no matter when you deploy, and if your maintenance window sits on an even hour this schedule walks straight into it. Because 2 divides 24 cleanly, nothing here warns you that a step which does not divide 24, such as */5, silently shortens the gap across midnight.
Platform snippets
The same expression, ready to paste into the four schedulers that read five-field cron. The notes below each snippet are the ones that follow from this schedule; the cron expression generator lists every caveat for every platform.
GitHub Actions
name: Scheduled workflow
on:
schedule:
- cron: '0 */2 * * *'
workflow_dispatch:
jobs:
scheduled:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run job
run: echo "Running scheduled job" - • Workflows scheduled at the top of the hour may be delayed during high-load periods. Consider offsetting the minute field (e.g. "5 */2 * * *") for more reliable timing.
Kubernetes CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: my-cron-job
spec:
schedule: "0 */2 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: job
image: my-image:latest
command: ["/bin/sh", "-c", "echo Running"]
restartPolicy: OnFailure Vercel Cron
Hobby plan limitation: cron jobs can only run once per day. Schedules more frequent than daily will fail to deploy on Hobby. Use Pro plan or above for higher frequency.
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"crons": [
{
"path": "/api/cron",
"schedule": "0 */2 * * *"
}
]
} - • Pro plan and above: invocations occur within the specified minute (e.g. "5 8 * * *" runs between 08:05:00 and 08:05:59).
crontab
# Edit your crontab: crontab -e
0 */2 * * * /path/to/command
# With logging:
0 */2 * * * /path/to/command >> /var/log/cron.log 2>&1 Variations
-
0 */3 * * *On the hour, every 3 hours Eight runs a day instead of twelve. Three also divides 24, so the gap survives midnight just as cleanly. -
0 */4 * * *On the hour, every 4 hours Six runs a day. The point at which a poll starts behaving like a batch, with room for a run to take tens of minutes. -
0 0,2,4,6,8,10,12,14,16,18,20,22 * * *At 12:00 AM, 02:00 AM, 04:00 AM, 06:00 AM, 08:00 AM, 10:00 AM, 12:00 PM, 02:00 PM, 04:00 PM, 06:00 PM, 08:00 PM and 10:00 PM The identical schedule as a list. Unwieldy at twelve values, which is exactly the case the step form exists for. -
30 */2 * * *At 30 minutes past the hour, every 2 hours The same even hours, shifted to half past. Costs nothing and keeps the job out of the top-of-hour rush on a shared host.
Frequently asked questions
How do I read the cron expression 0 */2 * * *?
Read the five space-separated fields left to right as minute, hour, day of month, month, and day of week. In 0 */2 * * * the minute is 0, hour is */2, day of month is *, month is *, day of week is *. Of the operators cron defines, this expression uses only the ones that matter here: an asterisk matches every value in its field and a slash introduces a step counted from the start of the field. Put together, cronstrue reads the whole thing as "On the hour, every 2 hours", and a scheduler that checks all five fields once a minute will start the job 12 runs per day.
What timezone does 0 */2 * * * run in?
The schedule repeats through the day at fixed hours — 00:00, 02:00, 04:00, 06:00, 08:00, 10:00, 12:00, 14:00, 16:00, 18:00, 20:00, 22:00 — and which part of the day those hours fall in depends entirely on the scheduler. Moving between zones does not change how often the job runs; it changes when. GitHub Actions defaults to UTC and takes an optional timezone key next to cron, so the hours can be anchored to an IANA zone. Vercel Cron always evaluates in UTC, with no setting to change it. Kubernetes CronJobs use the controller's local zone unless spec.timeZone is set, and a CRON_TZ prefix inside the schedule string is rejected outright. A crontab follows the host clock unless CRON_TZ overrides it. In any zone that observes daylight saving, the whole set of run hours shifts by one twice a year.
When does 0 */2 * * * fire the first run of the day?
The hour step counts from hour 0, so the run times are 00:00, 02:00, 04:00 and so on through 22:00 — twelve runs a day, always on even hours. Cron does not remember when the job last ran and does not offset the sequence from install time, which is why every host with this crontab fires in lockstep. To land on odd hours instead, use an explicit list such as 1,3,5,7,9,11,13,15,17,19,21,23 in the hour field, or shift the minute field to spread load without changing the hours.
Related cron schedules
-
0 * * * *Every hour -
0 */3 * * *Every 3 hours -
0 */4 * * *Every 4 hours -
0 */6 * * *Every 6 hours -
0 */12 * * *Every 12 hours -
0 0 * * *Every day at midnight
Build a custom schedule in the cron expression generator, or browse every epochkit tool.