Run Jobs Every Two Hours on the Hour | CronBase
0 */2 * * * Every two hours on the hour.
The `0 */2 * * *` cron expression executes a task every two hours at the start of the hour (minute zero). It triggers at midnight, 2:00 AM, 4:00 AM, and so forth throughout the day, providing a consistent bi-hourly cadence ideal for high-frequency maintenance and continuous data processing pipelines.
- Minute
- 0
- Hour
- */2
- Day of Month
- *
- Month
- *
- Day of Week
- *
Next 5 Runs
- in 1h 36m
- in 3h 36m
- in 5h 36m
- in 7h 36m
- in 9h 36m
* Tools
Code & Implementations
#!/usr/bin/env bash
# System cron entry: 0 */2 * * * /usr/local/bin/sync_assets.sh
set -euo pipefail
LOCKFILE="/var/lock/sync_assets.lock"
exec 9>"$LOCKFILE"
if ! flock -n 9; then
echo "Error: Asset synchronization job is already running." >&2
exit 1
fi
echo "Starting bi-hourly asset synchronization..."
# Perform actual work
if ! /usr/local/bin/perform_sync; then
echo "Error: Sync failed. Sending alert." >&2
exit 2
fi
echo "Synchronization completed successfully." › Setup notes
Save this script to your server, make it executable with chmod +x, and add the entry 0 */2 * * * /path/to/script.sh to your system crontab using crontab -e.
const cron = require('node-cron');
// Schedule task to run every two hours: 0 */2 * * *
cron.schedule('0 */2 * * *', async () => {
const startTime = new Date().toISOString();
console.log(`[${startTime}] Starting bi-hourly cache invalidation...`);
try {
await performCacheClean();
console.log(`[${new Date().toISOString()}] Cache invalidation completed successfully.`);
} catch (error) {
console.error(`[${new Date().toISOString()}] Critical error during cache invalidation:`, error.message);
// In production, integrate with Sentry, PagerDuty, or Datadog here
}
}, {
scheduled: true,
timezone: "Etc/UTC"
});
async function performCacheClean() {
return new Promise((resolve) => setTimeout(resolve, 5000));
} › Setup notes
Install the dependency using npm install node-cron and run this script as a background daemon process using PM2 or Docker.
import logging
import sys
from datetime import datetime
from apscheduler.schedulers.blocking import BlockingScheduler
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def execute_bi_hourly_backup():
logger.info("Initiating database backup process...")
try:
# Simulated backup logic
logger.info("Database backup completed successfully.")
except Exception as e:
logger.error(f"Database backup failed: {str(e)}", exc_info=True)
if __name__ == "__main__":
scheduler = BlockingScheduler()
scheduler.add_job(execute_bi_hourly_backup, 'cron', hour='*/2', minute=0, timezone='UTC')
logger.info("Scheduler initialized. Running every 2 hours on the hour.")
try:
scheduler.start()
except (KeyboardInterrupt, SystemExit):
logger.info("Scheduler stopped cleanly.")
sys.exit(0) › Setup notes
Install the required package via pip install apscheduler and execute the script. Ensure your system time is set to UTC.
package main
import (
"context"
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/robfig/cron/v3"
)
func main() {
logger := log.New(os.Stdout, "[Cron-Service] ", log.LstdFlags|log.Lshortfile)
c := cron.New(cron.WithLocation(time.UTC))
_, err := c.AddFunc("0 */2 * * *", func() {
logger.Println("Starting bi-hourly API ingestion pipeline...")
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
defer cancel()
if err := runIngestion(ctx); err != nil {
logger.Printf("CRITICAL: Ingestion failed: %v", err)
} else {
logger.Println("Ingestion pipeline completed successfully.")
}
})
if err != nil {
logger.Fatalf("Failed to schedule cron job: %v", err)
}
c.Start()
logger.Println("Scheduler running. Press Ctrl+C to exit.")
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
logger.Println("Shutting down scheduler gracefully...")
c.Stop()
}
func runIngestion(ctx context.Context) error {
return nil
} › Setup notes
Initialize a Go module, run go get github.com/robfig/cron/v3, paste the code into main.go, and execute go run main.go.
package com.example.scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.Instant;
@Component
public class BiHourlyDataSyncJob {
private static final Logger logger = LoggerFactory.getLogger(BiHourlyDataSyncJob.class);
@Scheduled(cron = "0 0 */2 * * *", zone = "UTC")
public void executeTask() {
logger.info("Starting bi-hourly data synchronization task at {}", Instant.now());
try {
performSynchronization();
logger.info("Bi-hourly data synchronization completed successfully.");
} catch (Exception e) {
logger.error("Error occurred during data synchronization: ", e);
}
}
private void performSynchronization() throws Exception {
Thread.sleep(2000);
}
} › Setup notes
Ensure @EnableScheduling is added to your Spring Boot main application class. Note that Spring uses a 6-field cron expression where the first field specifies seconds.
apiVersion: batch/v1
kind: CronJob
metadata:
name: bi-hourly-db-vacuum
namespace: database-ops
spec:
schedule: "0 */2 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5
startingDeadlineSeconds: 600
jobTemplate:
spec:
template:
spec:
containers:
- name: vacuum-worker
image: postgres:15-alpine
command:
- /bin/sh
- -c
- "vacuumdb -U postgres -d main_production --analyze-only"
env:
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
restartPolicy: OnFailure › Setup notes
Apply this manifest using kubectl apply -f cronjob.yaml. Ensure your Kubernetes cluster control plane operates in UTC to prevent timezone drifting.
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 */2 * * ? *) Systemd Timer
OnCalendar*-*-* 00/2:00:00
[Unit]
Description=Timer for cron expression: 0 */2 * * *
[Timer]
OnCalendar=*-*-* 00/2:00:00
Persistent=true
[Install]
WantedBy=timers.target
Last verified:
Frequently Asked Questions
How do I prevent overlapping runs if a job takes longer than two hours?
Implement a locking mechanism. In Kubernetes, set `concurrencyPolicy: Forbid`. On Linux servers, wrap your script in a utility like `flock` or utilize distributed lock managers like Redis (Redlock) or Consul to ensure only one instance runs at any given time.
What is the best way to handle Daylight Saving Time changes with this schedule?
Configure your system clock, cron daemon, or container runtime to use Coordinated Universal Time (UTC). Operating on UTC guarantees that the job executes consistently every 120 minutes without skipping or running twice during seasonal clock adjustments.
How does 0 */2 * * * differ from matching every minute like */120 * * * *?
Standard cron engines do not support step intervals larger than 59 in the minutes field. Therefore, `*/120` is invalid. The expression `0 */2 * * *` specifies execution specifically at minute zero of every second hour (e.g., 12:00, 2:00, 4:00).
How can I spread the load if multiple cron tasks run at the same time?
Avoid scheduling all tasks at minute `0`. Shift the execution minute to an arbitrary value (for example, `17 */2 * * *` or `43 */2 * * *`). This distributes network, memory, and CPU utilization across your server infrastructure.
How should I monitor this cron job to ensure it runs successfully?
Use a passive monitoring tool or 'dead man's snitch' that expects an HTTP ping every two hours. Configure a grace period of 2.5 hours. If the service fails to ping within that window, trigger an alert via PagerDuty, Slack, or email.
* Explore
Related expressions you might need
Last verified: