All posts

Cron Job to Run Every Hour: Syntax, Variations & Monitoring

September 2, 2026

The Cron Expression for Running a Job Every Hour

The cron expression to run a job every hour is 0 * * * *. That's the direct answer — but understanding why it works, and where it breaks down, is what keeps your scheduled jobs running.

A crontab line has five time fields, read left to right: minute, hour, day-of-month, month, and day-of-week. Each field accepts a number, a range, a comma-separated list, a step value, or an asterisk meaning "every value." In 0 * * * *, the 0 in the minute field means "at minute zero," and the four asterisks mean "every hour, every day of the month, every month, every day of the week." Put together, that's minute zero of every hour, forever — the pattern that shows up in almost every crontab handling recurring HTTP pings, cache refreshes, or report generation.

A real crontab line to run a script every hour on Linux looks like this:

0 * * * * /usr/bin/curl -s https://api.example.com/jobs/refresh

No wrapping, no special characters beyond the fields themselves. If you'd rather validate a pattern before saving it, crontab.guru is the standard sanity-check tool — paste an expression in and it tells you in plain English what it does. It's worth using any time you're editing a schedule you didn't write yourself.

One detail worth flagging early: the cron daemon evaluates time fields against the system's configured timezone, which on most servers defaults to UTC. If your team assumes "every hour" means every hour in your local business timezone, and the server runs UTC, the job still fires every hour — just offset from what you expect. That distinction matters more once we get into business-hours restrictions below.

Running Every 2, 3, or 6 Hours (and Other Variations)

Running a job every single hour is often overkill. Step values let you thin that out without switching tools. The syntax uses a slash after an asterisk: */N in the hour field means "every N hours, starting from hour 0."

  • Every 2 hours: 0 */2 * * *
  • Every 3 hours: 0 */3 * * *
  • Every 6 hours: 0 */6 * * *
  • Every hour at 15 minutes past: 15 * * * *
  • Every 4 hours, at 30 minutes past: 30 */4 * * *

A quick reference table for the most common variations:

Goal Cron expression
Every hour, on the hour 0 * * * *
Every hour, at :15 15 * * * *
Every 2 hours 0 */2 * * *
Every 3 hours 0 */3 * * *
Every 6 hours 0 */6 * * *
Every hour, at :45 45 * * * *

The offset-minute pattern matters more than it looks. If several jobs on the same server all fire at minute zero — deploy hooks, log rotation, your own scripts — they compete for CPU and I/O at the same instant. Shifting your job to 15 * * * * or 45 * * * * spreads the load without changing how often it runs, preventing a surprising amount of resource contention on busy hosts.

If you eventually need sub-hourly frequency — every 5 or 15 minutes — the mechanics are similar but the failure modes shift, since overlapping runs become far more likely; that's covered in our piece on running cron jobs every minute. If your actual need is monthly rather than hourly, see our guide to crontab once a month.

Restricting an Hourly Job to Business Hours or Specific Days

Not every hourly job needs to run 24/7. If you only want it firing during business hours or on weekdays, the hour and day-of-week fields do the work.

To run a job every hour, on the hour, from 9am to 5pm, Monday through Friday:

0 9-17 * * 1-5

The 9-17 is a range in the hour field, and 1-5 in the day-of-week field represents Monday (1) through Friday (5) — cron numbers Sunday as 0 (or sometimes 7, depending on the implementation). This is the pattern for an hourly cron job that's business-hours-only, rather than one that quietly burns compute and API quota at 3am when nobody's watching.

You can combine ranges with step values too — 0 9-17/2 * * 1-5 fires every 2 hours between 9am and 5pm on weekdays only. And remember the timezone caveat from above: "9-17" is relative to the cron daemon's timezone setting, not necessarily your team's local time. If the server runs UTC and your office is UTC-5, that 9-17 range covers 4am to noon local time — not the business day at all. Confirm the system timezone with timedatectl or by checking /etc/timezone before trusting a business-hours schedule.

Why Hourly Cron Jobs Silently Stop Running

Correct syntax gets a job scheduled. It doesn't guarantee the job keeps executing indefinitely, and hourly jobs fail in a handful of specific, recurring ways.

Server reboots. User crontabs generally persist across reboots on most Linux distributions, but if a job was added to a container image, a temporary crontab, or a config-management script that never re-ran, a reboot can wipe it clean. The schedule looks fine in your notes; it simply isn't installed anymore.

Overlapping runs. If a job scheduled for every hour occasionally takes longer than 60 minutes — a slow API call, a database lock, an unexpectedly large dataset — cron happily starts the next instance anyway. Now two copies run against the same resources, and depending on what the job touches, that can corrupt state or silently double-process data rather than throwing an obvious error.

DST and timezone shifts. Does daylight saving time affect an hourly cron job? If the cron daemon's timezone observes DST, the transition can cause an hour to be skipped or repeated once a year, and any hour-range restriction (like the 9-17 business-hours example) shifts by an hour relative to real-world clock time until the next changeover. Servers running UTC avoid the DST issue entirely, since UTC never shifts — but that just moves the confusion to "what UTC hour equals 9am local," which is its own trap if nobody documents it.

Cron daemon or clock issues. A stopped cron/crond service, a system clock that's drifted, or a systemd timer misconfigured to replace cron without matching its schedule can all cause a job to stop firing with zero error output — because there's nothing running to report an error in the first place.

How to Confirm Your Hourly Job Actually Ran

None of this failure list produces a log entry, an email, or a Slack message by default. Crontab has no built-in way to confirm a job executed — it just attempts the run and moves on, whether the run succeeded, failed, or never started at all.

The practical fix is a check-in model: your job pings a monitoring endpoint on success, and you set an expectation of "a check-in every hour." If an hour passes with no ping, that's your signal something's wrong — a dead man's switch, essentially, watching for the absence of a heartbeat rather than waiting for an explicit failure report. The mechanics of this pattern — pull checks, push pings, and heartbeat monitoring — are broken down in our guide to healthcheck systems, and the broader monitoring strategy is covered in our piece on catching silent cron failures.

This is exactly the gap Cronevra is built to close. Point your hourly job at a Cronevra check-in URL, set the expected interval to match your schedule, and get alerted the moment a run is late, missing, or overlapping — instead of finding out from a downstream report that's three hours stale. If you're ready to see how it fits your setup, Cronevra's pricing page lays out the plans for teams running a handful of jobs or several hundred.

Frequently Asked Questions

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

0 * * * * runs a job at minute zero of every hour, every day. The five fields are minute, hour, day-of-month, month, and day-of-week, and setting the minute to 0 while leaving the rest as asterisks means "top of every hour, no other restrictions."

How do I run a job every N hours instead of every single hour?

Use a step value in the hour field: 0 */2 * * * runs every 2 hours, 0 */3 * * * every 3 hours, and 0 */6 * * * every 6 hours. The */N syntax tells cron to fire on every Nth hour starting from hour zero, and you can pair it with a specific minute instead of 0 if you want an offset.

How do I make an hourly cron job run only during certain hours or days?

Combine an hour range with a weekday range, such as 0 9-17 * * 1-5 for every hour between 9am and 5pm on weekdays. The hour field accepts ranges like 9-17, and the day-of-week field accepts 1-5 for Monday through Friday, letting you avoid true round-the-clock execution.

Why might a correctly-written hourly cron job fail to execute?

Common causes include a crontab wiped by a server reboot, overlapping runs when a job takes longer than an hour, a stopped cron daemon, or system clock drift. None of these produce an error by default — the job simply doesn't run, and nothing reports it.

Does DST or timezone change affect when an hourly cron job fires?

Yes, if the cron daemon's timezone observes daylight saving time, the spring-forward and fall-back transitions can cause an hour to be skipped or run twice, and any hour-range schedule shifts relative to real-world clock time. Running the server on UTC avoids the DST shift itself, but you then need to translate any "business hours" range into the correct UTC offset.