Run Jobs Hourly at the Top of the Hour | CronBase
0 * * * * At the beginning of every hour.
This cron expression schedules a task to run exactly once per hour, specifically at the very beginning of the hour (minute zero). It is widely used in production environments for recurring tasks like generating hourly metrics, purging expired session caches, pulling external API updates, and rotating small log files.
- Minute
- 0
- Hour
- *
- Day of Month
- *
- Month
- *
- Day of Week
- *
Next 5 Runs
- in 36m 20s
- in 1h 36m
- in 2h 36m
- in 3h 36m
- in 4h 36m
* Tools
Code & Implementations
#!/usr/bin/env bash
# Hourly maintenance task with flock to prevent overlapping executions
set -euo pipefail
LOCKFILE="/var/lock/hourly_maintenance.lock"
exec 9>"$LOCKFILE"
if ! flock -n 9; then
echo "Error: Another instance of the hourly job is still running." >&2
exit 1
fi
echo "[$(date -u)] Starting hourly database cleanup..."
# Perform cleanup operation here
# e.g., pg_dump or temporary file removal
echo "[$(date -u)] Hourly cleanup completed successfully." › Setup notes
Save this script to /usr/local/bin/hourly-cleanup.sh, make it executable, and add '0 * * * * /usr/local/bin/hourly-cleanup.sh' to your crontab using 'crontab -e'.
const cron = require('node-cron');
// Schedule the task to run at minute 0 of every hour
cron.schedule('0 * * * *', async () => {
console.log(`[${new Date().toISOString()}] Initiating hourly cache invalidation...`);
try {
await clearExpiredSessions();
console.log('Hourly cache invalidation completed.');
} catch (error) {
console.error('Unexpected execution error during hourly task:', error);
}
});
async function clearExpiredSessions() {
// Production session clearing logic goes here
return Promise.resolve();
} › Setup notes
Install the 'node-cron' package using npm or yarn, then run this script as a daemon or background process using a process manager like PM2.
import logging
from apscheduler.schedulers.blocking import BlockingScheduler
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def hourly_data_sync():
logger.info("Starting hourly data synchronization pipeline...")
try:
# Real-world ETL / API synchronization logic goes here
pass
logger.info("Hourly data synchronization finished successfully.")
except Exception as e:
logger.error(f"Data synchronization failed: {e}", exc_info=True)
if __name__ == "__main__":
scheduler = BlockingScheduler()
# '0 * * * *' translates to running at minute 0 of every hour
scheduler.add_job(hourly_data_sync, 'cron', minute=0)
logger.info("Hourly scheduler initialized.")
try:
scheduler.start()
except (KeyboardInterrupt, SystemExit):
logger.info("Scheduler stopped cleanly.") › Setup notes
Install 'apscheduler' using pip (pip install apscheduler) and run this script. It will run in the foreground and execute the task on the hour.
package main
import (
"log"
"os"
"os/signal"
"syscall"
"github.com/robfig/cron/v3"
)
func main() {
logger := log.New(os.Stdout, "HOURLY_JOB: ", log.LstdFlags|log.Lshortfile)
// Initialize cron scheduler with standard 5-field parser
c := cron.New(cron.WithParser(cron.NewParser(
cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor,
)))
_, err := c.AddFunc("0 * * * *", func() {
logger.Println("Executing hourly log rotation and metrics flush...")
if err := performHourlyTask(); err != nil {
logger.Printf("ERROR: Hourly task failed: %v\n", err)
}
})
if err != nil {
logger.Fatalf("Failed to schedule job: %v", err)
}
c.Start()
logger.Println("Scheduler started. Waiting for execution...")
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
logger.Println("Shutting down scheduler gracefully...")
c.Stop()
}
func performHourlyTask() error {
return nil
} › Setup notes
Initialize a Go module, run 'go get github.com/robfig/cron/v3' to fetch the library, and run this program to manage the background hourly job.
package com.example.cron;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component
public class HourlyTaskScheduler {
private static final Logger logger = LoggerFactory.getLogger(HourlyTaskScheduler.class);
// Spring's @Scheduled uses a 6-field pattern (second, minute, hour, day, month, weekday)
// "0 0 * * * *" means at second 0, minute 0 of every hour
@Scheduled(cron = "0 0 * * * *")
public void runHourlyMaintenance() {
logger.info("Starting hourly database index optimization...");
try {
optimizeIndexes();
logger.info("Hourly index optimization completed.");
} catch (Exception e) {
logger.error("Error during index optimization", e);
}
}
private void optimizeIndexes() {
// Logic to execute database index optimization
}
} › Setup notes
Ensure '@EnableScheduling' is added to your Spring Boot application's main configuration class, and place this component within your component scan path.
apiVersion: batch/v1
kind: CronJob
metadata:
name: hourly-data-pruner
namespace: default
spec:
schedule: "0 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
containers:
- name: pruner
image: alpine:3.18
command:
- /bin/sh
- -c
- "echo 'Starting pruning...'; sleep 10; echo 'Pruning complete.'"
restartPolicy: OnFailure › Setup notes
Apply this manifest using 'kubectl apply -f cronjob.yaml'. The 'concurrencyPolicy: Forbid' setting ensures that if a job runs over 60 minutes, a second container is not spun up.
Last verified:
Monitor this schedule in production
Get alerted the moment this cron job fails, is late, or doesn't run. BetterStack tracks execution, duration, and output — no infrastructure required.
Platform Equivalents
AWS EventBridge
Standard cron expressions often need conversion for AWS EventBridge schedules.
cron(0 * * * ? *) Systemd Timer
OnCalendar*-*-* *:00:00
[Unit]
Description=Timer for cron expression: 0 * * * *
[Timer]
OnCalendar=*-*-* *:00:00
Persistent=true
[Install]
WantedBy=timers.target
Last verified:
Frequently Asked Questions
How does `0 * * * *` handle Daylight Saving Time (DST) transitions?
Standard system cron runs on UTC to avoid DST anomalies. If your server is configured to local time, the job may execute twice during the autumn fallback or skip execution entirely during the spring spring-forward transition.
What is the difference between `0 * * * *` and `*/60 * * * *`?
In standard cron, both expressions execute at minute zero of every hour. However, `0 * * * *` is preferred for readability and explicit behavior, whereas step syntax like `*/60` can sometimes be interpreted differently depending on the specific cron daemon implementation.
How do I prevent multiple instances of this hourly job from running concurrently?
Use file locks (like `flock` in Bash), distributed locks (like Redis/Redlock in application code), or set `concurrencyPolicy: Forbid` in Kubernetes CronJobs to prevent a slow-running execution from overlapping with the next hour's run.
Can I run this job at a specific minute other than zero?
Yes. If you want to run the job hourly but avoid the "top-of-the-hour" rush, simply change the first field. For example, `15 * * * *` will run the job at 15 minutes past every hour, distributing resource load.
How can I test my `0 * * * *` schedule without waiting an hour?
For local testing, temporarily change the schedule to `*/1 * * * *` (every minute) or trigger the target script/command directly in your terminal to verify execution logic before deploying the hourly cron schedule.
* Explore
Related expressions you might need
Last verified: