Cron expression for every hour
The cron expression 0 * * * *
runs every hour. The restricted fields are minute 0; the remaining asterisks match anything. That works out to 24 runs per day.
0 * * * * Field breakdown
| Field | Value | Meaning |
|---|---|---|
| Minute | 0 | The top of the hour (:00) |
| Hour | * | Every hour (0–23) |
| 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 * * * * — 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 "Every hour": 24 runs per day. With the hour left open and the minute pinned to 0, the job fires once in every hour of the day, at :00 — twenty-four times between one midnight and the next.
When to use every hour
Hourly is the workhorse cadence for rollups, log rotation, pruning temporary files, and pulling an upstream feed that publishes on the hour. Twenty-four runs a day keep the aggregate cost negligible while bounding staleness to sixty minutes. The thing to watch is the crowd: pinning the minute to 0 puts this job on the same tick as every other hourly job on the host and, on GitHub Actions, in the same queue as a large slice of the platform. If the exact minute does not matter, move it off :00.
Common mistakes with every hour
The single most common cron bug lives in this expression's first field. Write * * * * * instead of 0 * * * * and the job jumps from 24 runs a day to 1,440 with no error, no warning, and almost no visual difference in the crontab. Read the minute field first whenever a job is running more often than it should. The other hourly-specific mistake is leaving the minute at 0 out of habit: everything else scheduled on the machine also fires at :00, and on GitHub Actions the top of the hour is the busiest queue on the platform. Move to :07 and the contention disappears.
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 * * * *'
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 * * * *") for more reliable timing.
Kubernetes CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: my-cron-job
spec:
schedule: "0 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: job
image: my-image:latest
command: ["/bin/sh", "-c", "echo Running"]
restartPolicy: OnFailure - • Kubernetes supports shorthand macros: @yearly, @monthly, @weekly, @daily, @hourly.
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 * * * *"
}
]
} - • 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 * * * * /path/to/command
# With logging:
0 * * * * /path/to/command >> /var/log/cron.log 2>&1 - • Most modern implementations (vixie-cron, cronie) support shorthand macros: @yearly, @monthly, @weekly, @daily, @hourly, @reboot. These are not defined by POSIX — check your platform.
Variations
-
30 * * * *At 30 minutes past the hour Hourly at half past. Identical cadence, but it leaves the contested :00 tick to everything else on the host. -
15,45 * * * *At 15 and 45 minutes past the hour Twice an hour, at :15 and :45. Doubles the frequency and still avoids the top of the hour entirely. -
0 */2 * * *On the hour, every 2 hours Every other hour on the hour — twelve runs a day. The next step down when hourly turns out to be more often than the data changes. -
0 9-17 * * 1-5Every hour, between 09:00 AM and 05:00 PM, Monday through Friday Hourly, but only during business hours on weekdays: 45 runs a week instead of 168, with the overnight and weekend runs dropped as pure cost.
Frequently asked questions
How do I read the cron expression 0 * * * *?
Read the five space-separated fields left to right as minute, hour, day of month, month, and day of week. In 0 * * * * the minute is 0, hour is *, 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. Put together, cronstrue reads the whole thing as "Every hour", and a scheduler that checks all five fields once a minute will start the job 24 runs per day.
What timezone does 0 * * * * run in?
Less than you would think. Because the hour field is unrestricted, the cadence is the same in every timezone — 24 runs per day, wherever the scheduler's clock is set. The zone changes the labels on the run times rather than the schedule. It still matters at the edges. GitHub Actions evaluates schedules in UTC unless the workflow adds the optional timezone key beside cron; Vercel Cron is UTC-only; a Kubernetes CronJob follows the kube-controller-manager's zone unless spec.timeZone is set; and a crontab follows the host clock or CRON_TZ. The edge that bites is daylight saving: in a zone that shifts its clocks, the spring-forward hour never arrives and the fall-back hour arrives twice, so a schedule like this one loses or repeats a whole block of runs on those two days a year. Scheduling in UTC avoids it.
What is the difference between 0 * * * * and * * * * *?
The first field is the minute. In 0 * * * * the minute is pinned to 0, so the job runs once per hour at the top of the hour; in * * * * * the minute is unrestricted, so the job runs on all sixty minutes of every hour. Leaving an asterisk in the minute field is the most common cron mistake — 0 * * * * gives 24 runs a day, while * * * * * gives 1,440. If your job suddenly runs constantly, check the minute field before anything else.
Related cron schedules
-
0 */2 * * *Every 2 hours -
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.