Field breakdown
The minute field is the single value 0; everything else is a wildcard. The job is
therefore due once per hour, at :00 — 24 runs a day. Cron also accepts the shorthand
@hourly, which the daemon expands to precisely 0 * * * *.
Watch the field order. 0 * * * * is hourly. * 0 * * * is the opposite:
every minute during the midnight hour, sixty runs between 00:00 and 00:59 and nothing for the rest of
the day.
Do not run it exactly on the hour
Almost every hourly job in the world fires at :00. Log rotation, metric rollups, cache expiries, billing meters and everyone else's crontab all converge on that minute, and shared infrastructure feels it. Unless the schedule is externally mandated, offset it:
17 * * * * /usr/local/bin/hourly-rollup.sh
A minute chosen from the middle of the hour costs nothing, spreads the load and makes your job's entries trivial to spot in a shared log.
Using it
# crontab, both forms are equivalent
0 * * * * /usr/local/bin/hourly.sh
@hourly /usr/local/bin/hourly.sh
# Kubernetes CronJob
spec:
schedule: "0 * * * *"
timeZone: "Europe/London"
Kubernetes accepts a timeZone field; plain cron does not, and uses the system zone
unless the crontab sets CRON_TZ. For an hourly job the distinction rarely matters, since
every zone offset is a whole or half hour and the job runs every hour regardless.
Variations
| Expression | Runs |
|---|---|
| 0 * * * * | Every hour at :00. Same as @hourly. |
| 17 * * * * | Every hour at :17 — off the busy boundary. |
| 0 */2 * * * | Every two hours: 00:00, 02:00, 04:00 and so on. |
| 0 */6 * * * | Four times a day: 00:00, 06:00, 12:00 and 18:00. |
| 0 9-17 * * 1-5 | Hourly during office hours on weekdays. |
| 0 * * * 1-5 | Hourly, weekdays only. |