The 0 * * * * Expression
The cron expression 0 * * * * runs a job at minute 0 of every hour. The five fields are minute, hour, day of month, month, day of week. Setting the minute to 0 and using * for everything else means the job fires at 00:00, 01:00, 02:00, and so on, exactly 24 times per day.
Why the Minute Field Matters
The single biggest source of accidental runaway cron jobs is confusing * * * * * with 0 * * * *.
* * * * * # runs every minute, 1440 times/day
0 * * * * # runs every hour, 24 times/day
If you want a job to run hourly and you write * * * * *, you will not notice the problem until you check your logs and see it has been running all day. Always double check the minute field for hourly schedules.
The @hourly Shorthand
Most cron implementations support a set of @ shortcuts:
| Shorthand | Equivalent |
|---|---|
@hourly | 0 * * * * |
@daily | 0 0 * * * |
@weekly | 0 0 * * 0 |
@monthly | 0 0 1 * * |
@yearly | 0 0 1 1 * |
@reboot | runs once at startup |
@hourly is supported in Vixie cron (the default on most Linux distributions), cronie, and many managed cron services. It is not part of the POSIX standard, so if you are writing a portable crontab or using a more minimal cron implementation, stick with 0 * * * *.
Staggering Hourly Jobs
If you have multiple services all running an hourly job, scheduling them all at 0 * * * * creates a thundering herd at the top of every hour. Everything fires simultaneously, databases get hit at once, and downstream services see a usage spike every 60 minutes.
The fix is to offset each job by a few minutes:
0 * * * * /opt/jobs/report-generator
7 * * * * /opt/jobs/cache-warmer
14 * * * * /opt/jobs/data-sync
21 * * * * /opt/jobs/cleanup
These four jobs now each run at their own minute mark past the hour, spreading the load evenly. The offsets are arbitrary. The point is that they do not all overlap.
For larger fleets, randomized offsets (picked at deployment time and baked into the crontab) work better than hand chosen ones, since developers tend to pick the same “obvious” offsets.
When Hourly Is the Right Choice
Report generation
Hourly summaries are a natural boundary for dashboards showing “last hour” metrics. Generating the report at the top of the hour (or just after, with a small offset) ensures the data covers a clean window.
External API sync
If you are pulling data from an external service and the data changes at most once per hour, polling more frequently wastes quota. An hourly cron aligned with the data’s update cadence is correct.
Token or session refresh
Short lived credentials that expire every few hours can be refreshed on an hourly basis as a safety margin. This is simpler than trying to detect expiry and refresh on demand.
Cleanup tasks
Deleting temporary files, purging expired cache entries, or archiving old records is often done hourly. The exact timing usually does not matter, so this is also a good candidate for an offset schedule.
When Hourly Is Too Infrequent
If the acceptable latency for detecting a problem or syncing data is less than an hour, cron is still an option but you need a shorter interval like */5 * * * * or */15 * * * *. If you need subminute granularity or guaranteed execution timing, consider a dedicated scheduler, a message queue with a delay, or a long running process with an internal loop.
Confirming the Next Run Times
To verify the next execution times for 0 * * * *, use the cron builder tool on this page, or check with Python:
from croniter import croniter
from datetime import datetime
cron = croniter("0 * * * *", datetime.now())
for _ in range(5):
print(cron.get_next(datetime))
You can also confirm the schedule is active on a Linux system with crontab -l and verify execution with grep CRON /var/log/syslog or journalctl -u cron.