All posts

Cron Every Hour: Correct Syntax, Variants & Pitfalls

September 22, 2026

The Cron Expression for Every Hour

The direct answer: 0 * * * * runs a job at the top of every hour. That's the standard, correct way to express cron every hour in a crontab file or scheduler config.

What each field does:

  • Minute (0) — fires at :00 of the hour.
  • Hour (*) — every hour, no restriction.
  • Day of month, month, day of week (* * *) — wildcards, so the schedule applies every day, every month, regardless of weekday.

Read together, 0 * * * * means "at minute 0 of every hour, every day." For a full breakdown of how each field works across other scheduling patterns, see What Is a Cron Expression? Syntax, Fields & Examples.

Most cron implementations — including Vixie cron and modern schedulers like systemd timers with cron-style syntax — also support @hourly as shorthand, functionally equivalent to 0 * * * * in the vast majority of setups. It's worth checking your specific platform's documentation, since a small number of non-standard parsers treat shorthand strings differently.

Common Mistake: */1 vs 0 * * * *

A frequent point of confusion is using */1 instead of a plain wildcard. In the hour field, */1 * * * technically still means "every hour," because */1 is a step value that matches every unit starting from the field's minimum. So */1 and * in the hour position produce the same result — but they're not the same thing conceptually, and mixing them up elsewhere causes real bugs.

The actual mistake happens when developers put */1 in the minute field instead, writing something like */1 * * * *. That expression runs every single minute, not every hour — a very different (and often costly) schedule. The clean, unambiguous way to say "once every hour, at minute 0" is 0 * * * *: zero in the minute field pins the exact trigger point, and the wildcard hour field lets it recur every hour. Avoid */1 cron patterns unless you specifically mean "every 1 unit of this field," and always double-check which field you're modifying before deploying.

Running Every 2, 3, 6, or 12 Hours Instead

If hourly is too frequent, step values in the hour field give clean variants without changing the minute field:

  • Every 2 hours: 0 */2 * * *
  • Every 3 hours: 0 */3 * * *
  • Every 6 hours: 0 */6 * * *
  • Every 12 hours: 0 */12 * * *

Each keeps the job anchored to minute 0, so runs stay predictable and aligned to the clock (00:00, 02:00, 04:00, etc., for the every-2-hours example). The step value must divide evenly into 24 to stay aligned with midnight — */5, for instance, produces an irregular pattern since 24 isn't evenly divisible by 5. For the full rules on valid values in each field, see Cron Values Explained: Valid Ranges for Every Field.

Why "Every Hour" Doesn't Always Mean Reliable

Getting the expression right only solves half the problem. Cron guarantees a schedule, not a guaranteed outcome, and several real-world factors can quietly break an hourly job.

Server timezone settings are the most common surprise. Cron typically runs in the system's local timezone unless explicitly configured otherwise. If your server's timezone changes — through a migration, a container rebuild, or a misconfigured TZ environment variable — "every hour" still fires, but at different wall-clock times than expected, throwing off anything downstream that assumes a specific hour.

DST transitions compound this. During a spring-forward shift, an hour is skipped entirely, so a job scheduled for that clock hour may not run at all that day. During a fall-back shift, some systems execute the affected hour's job twice. This cron timezone DST behavior catches teams off guard because the expression itself never changed — only the clock did.

System reboots and downtime cause a different failure. If the machine is down at :00, that run simply doesn't happen — there's no catch-up execution once the box comes back unless you've built retry logic yourself. This is how an hourly cron job skipped incident typically starts: nothing in the logs looks wrong, because there's no error, just an absence.

Resource contention at :00 is the quieter issue. Because so many cron jobs across so many systems default to minute 0, you get a predictable spike in CPU, database connections, or outbound network calls exactly when your job fires. A job that works fine at :07 might time out or get throttled at :00 simply because it's competing with everything else scheduled the same way. If you've seen a cron job not running every hour despite an unchanged config, contention at the trigger point is a common, overlooked culprit.

How to Confirm Your Hourly Job Actually Ran

A syntactically correct expression tells cron when to attempt a run — it says nothing about whether that run succeeded, or ran at all. Logs and MAILTO notifications were built for a different era of scale: they work when you're actively watching, but don't scale to catching one missed execution out of 24 a day, especially when the failure is silent — no error thrown, no email sent, just a job that never started.

This is where monitoring an hourly cron job earns its place. Instead of relying on you to notice a gap in the logs, an expected-timeframe alert flips the model: you tell the monitor "this should run every hour," and if a check-in doesn't arrive within that window, you get notified immediately. That single change turns hourly cron job monitoring from a manual log review into an automatic safety net — and it's the only reliable way to catch a silent cron failure before it becomes a bigger outage. For teams thinking beyond a single job, Automated Health Checks: A Practical Guide for Teams covers building that safety net across your whole scheduled workload.

Once your 0 * * * * expression is deployed, the only way to know it's actually firing — hour after hour, through reboots, DST shifts, and resource spikes — is to watch it from the outside. Cronevra alerts you the moment an expected hourly run is missed or fails, so a silent skip never turns into a silent outage. Check Pricing to see which plan fits your job count.

Frequently Asked Questions

What is the cron expression to run a job every hour?

The expression is 0 * * * *. The 0 fixes the minute at the top of the hour, and the wildcard hour field lets it repeat every hour, every day, every month.

What does 0 * * * * mean in crontab?

It means "run at minute 0 of every hour, every day of the month, every month, every day of the week." In plain terms, the job fires once per hour, exactly at the top of the hour (e.g., 1:00, 2:00, 3:00).

Is @hourly the same thing as 0 * * * *?

Yes, in most standard cron implementations, @hourly is shorthand that expands to 0 * * * *. Confirm with your specific scheduler's documentation, since a small number of non-standard parsers handle shorthand strings differently.

How do I set up a cron job to run every 2 hours instead of every hour?

Use a step value in the hour field: 0 */2 * * *. This keeps the job anchored to minute 0 while triggering every second hour (00:00, 02:00, 04:00, and so on).

Why does my hourly cron job sometimes skip a run even though the syntax is correct?

A correct expression only guarantees the schedule, not execution. Common causes of a missed run include server downtime or reboots at the trigger time, DST transitions that skip or duplicate an hour, timezone changes on the host, and resource contention when many jobs fire simultaneously at :00.

How can I get alerted if my hourly cron job fails or doesn't run?

Set up expected-timeframe monitoring rather than relying on logs or email alerts alone. A monitoring service that expects a check-in every hour will notify you immediately if that check-in doesn't arrive, catching silent failures that logs alone would miss.