Cron Hourly: Syntax, Silent Failures, and Real Monitoring
August 24, 2026


What "Cron Hourly" Actually Means
"Cron hourly" gets used loosely, and that ambiguity causes real bugs. It can mean a job that fires at the top of every hour, one that runs every N hours (every 2, 4, or 6), or one offset to a specific minute — say, 15 minutes past each hour to avoid the top-of-hour traffic spike that every other scheduled job also targets.
An hourly cron schedule isn't a single pattern — it's a family of schedules that share one property: they run often enough that failures compound quickly. Miss one run of a daily job and you have a full day to notice. Miss several runs of an hourly job and you might not notice until a report is empty, a sync is stale, or a customer complains. That's the operational reality this article is really about — not just getting the syntax right, but keeping the schedule honest once it's live.
Quick Syntax: Every Hour and Every N Hours
The minimal cron every hour syntax is five fields, with the minute fixed and everything else wildcarded:
0 * * * * /path/to/script.sh
This runs once, at minute 0 of every hour. If you want to avoid the exact top of the hour (a common tactic to dodge shared load on busy hosts), just change the minute field:
15 * * * * /path/to/script.sh
For a cron every 2 hours syntax, use a step value in the hour field:
0 */2 * * * /path/to/script.sh
Every 6 hours follows the same pattern — 0 */6 * * *. Note that */N divides evenly from midnight, so */6 runs at 00:00, 06:00, 12:00, 18:00, not relative to whenever you deployed the job. This behaves consistently across crontab, cronie, and Vixie cron, the implementations most Linux distributions ship by default. If you need finer control — like running only during business hours — that's a deeper topic than this article covers; a dedicated crontab every-hour reference is worth consulting for edge cases like comma-separated hour lists or day-of-week restrictions.
Why Hourly Jobs Fail Silently in Production
Getting the expression right is the trivial part. The failure modes that actually cause pain show up after the job has been running fine for weeks.
Overlapping runs. Cron doesn't check whether the previous invocation finished — it just fires on schedule. If a job normally takes five minutes but occasionally takes 70 due to a slow API, a locked database row, or a larger-than-usual batch, the next hourly trigger starts a second instance while the first is still running. Now you have two processes touching the same data, doubling API calls, or racing to write the same file. This is especially common once jobs run across multiple servers — see Distributed Job Scheduling: How It Works, Where It Fails for how overlap turns into duplicate processing at scale.
DST transitions. Cron typically runs on the system's local timezone unless configured otherwise. When clocks spring forward, the hour that "disappears" means a job scheduled for that hour simply never fires. When clocks fall back, the repeated hour can cause a job to run twice. These cron DST issues are invisible in your code — the script is fine, the trigger just didn't happen, or happened an extra time.
Reboots, deploys, and container restarts. Traditional cron only runs while the daemon is alive and the schedule is loaded. A server reboot, an OS package upgrade, or a container redeploy can silently drop the crontab entry, especially in containerized setups where cron isn't the process manager's first-class citizen. The job doesn't error — it just stops existing, and a missed cron job hourly can go unnoticed for days if nothing is watching for absence.
Unreliable default mail. Cron's built-in behavior is to email job output to the local user, assuming an MTA is configured. On most modern servers, it isn't. Even when it is, that mail account is rarely checked. This gives teams false confidence: they assume they'd "get an email" if something broke, when in practice no email was ever going to arrive.
Building Hourly Jobs That Don't Quietly Break
A few deliberate practices remove most of the risk above:
Pin your timezone. Don't rely on server local time by default. Either standardize on UTC for all schedules, or set CRON_TZ explicitly at the top of the crontab for jobs that must align with a business timezone. This makes DST behavior predictable and documented rather than accidental.
Add locking, not just intent. Use a file lock (flock), a database advisory lock, or a lock key in Redis to guarantee only one instance of a job runs at a time. This is cheap insurance against overlapping runs and doesn't require rewriting the job itself.
Design for idempotency. Even with locking, assume a run might partially complete or execute twice. Structure jobs so re-running them with the same inputs produces the same result — upsert instead of insert, check-before-write instead of blind append. Idempotency turns a duplicate run from a data corruption event into a harmless no-op.
Keep runtime well under the interval. If a job runs hourly, it should reliably finish in a small fraction of that hour, with margin for slow days. If it regularly creeps toward 45–50 minutes, that's a signal to optimize the job or increase the interval, not a coincidence to ignore.
Avoid scheduling time-sensitive jobs in the 1–3 AM DST window if your fleet uses local time. It's a narrow fix, but it eliminates the single most common DST-related support ticket.
For system-level alternatives, systemd timers offer overlap protection (Persistent=true, unit dependency ordering) that raw crontab entries don't, though they trade some of cron's simplicity for that control.
How to Actually Know an Hourly Job Ran
Logging and cron's mail output tell you what happened if the job ran and if something checked the output. They can't tell you a job didn't run at all — there's no error to log, no email to send, because nothing executed. This is the core limitation of passive visibility.
The reliable alternative is active monitoring built on a dead man's switch pattern: your job pings a monitoring endpoint on every successful run, and the monitoring service raises an alert if that ping doesn't arrive within the expected window. This differs fundamentally from uptime checks or health endpoints, which test whether a service is reachable, not whether a specific scheduled task executed. Monitoring Check Types Explained: Which One Catches Cron breaks down exactly which check type catches a missed hourly run versus which ones give false confidence.
For hourly cron job monitoring, this heartbeat approach catches every failure mode covered above: a missed reboot-related run shows up as a missing ping, an overlapping run shows up as two pings closer together than expected, and a DST skip shows up as a gap exactly one hour wide. That's the practical way to monitor a cron job that runs every hour — not by reading logs after the fact, but by expecting a signal and alerting the moment it doesn't arrive.
Cronevra is built around that model: your hourly jobs report in, and Cronevra tells you immediately when one goes quiet, overlaps, or drifts — instead of leaving you to discover it from a downstream complaint.
Frequently Asked Questions
What is the cron syntax to run a job every hour?
Use 0 * * * * to run at the top of every hour, or shift the minute field (e.g., 15 * * * *) to run at a specific minute past each hour. All five fields besides the minute stay as wildcards.
Why does my hourly cron job sometimes run twice or not at all?
The two most common causes are DST transitions, which duplicate or skip an hour depending on the direction of the clock change, and overlapping runs, where a slow execution is still finishing when cron fires the next trigger. Server reboots or crontab reloads can also silently drop a scheduled run.
How do I run a cron job every 2 or 6 hours instead of every hour?
Use a step value in the hour field: 0 */2 * * * for every 2 hours, 0 */6 * * * for every 6 hours. These fire relative to midnight, not relative to when the job was first deployed.
Does daylight saving time affect hourly cron jobs?
Yes, if cron is running on local time rather than UTC. The clock-forward transition eliminates an hour, skipping any job scheduled inside it, while the clock-back transition repeats an hour, potentially running a job twice.
How can I tell if an hourly cron job silently failed?
Passive methods like log files and cron's default mail can't tell you a job didn't run, since nothing executes to generate an error. Active heartbeat monitoring, where the job pings a service on success and an alert fires if the ping is missing, is the only reliable way to catch a truly silent failure.
What's the difference between an hourly cron job and a cron job that runs "once an hour" via a loop or queue?
A cron job is scheduled and triggered by the OS or scheduler at fixed times, independent of the application process. A loop or queue-based "hourly" task runs inside a long-lived application process using a sleep or delay, which means it stops entirely if that process crashes or is deployed, unlike cron, which reschedules automatically as long as the crontab is loaded.
Ready to stop finding out about missed hourly jobs from a customer instead of a monitor? Cronevra adds heartbeat monitoring to your existing hourly cron jobs in minutes — catching missed runs, overlaps, and DST-shifted schedules the moment they happen. Check Pricing to find a plan that fits your job count and get alerted before silence becomes an incident.