Run Every 30 Minutes Cron Schedule | CronBase
*/30 * * * * Every half hour, running on the hour and thirty minutes past the hour, every day.
The `*/30 * * * *` cron expression schedules a task to execute every thirty minutes, specifically at the top of the hour and thirty minutes past the hour. This high-frequency cadence is commonly used for automated system health checks, periodic database cache invalidation, API synchronization, and active monitoring tasks.
- Minute
- */30
- Hour
- *
- Day of Month
- *
- Month
- *
- Day of Week
- *
Next 5 Runs
- in 6m 19s
- in 36m 19s
- in 1h 6m
- in 1h 36m
- in 2h 6m
* Tools
Code & Implementations
#!/usr/bin/env bash
# Cron integration: Add this line to your crontab using 'crontab -e':
# */30 * * * * /usr/local/bin/db_cleanup.sh >> /var/log/db_cleanup.log 2>&1
set -euo pipefail
LOCKFILE="/var/tmp/db_cleanup.lock"
# Use flock to prevent concurrent execution if a previous run hangs
exec 9>"$LOCKFILE"
if ! flock -n 9; then
echo "[$(date -u)] Warning: Another instance of db_cleanup is already running. Exiting." >&2
exit 1
fi
echo "[$(date -u)] Starting database cleanup process..."
# Simulated payload
if ! pg_dump -U postgres -d production_db --schema-only > /dev/null; then
echo "[$(date -u)] Error: Database backup check failed!" >&2
exit 2
fi
echo "[$(date -u)] Database cleanup completed successfully." › Setup notes
Save this script to /usr/local/bin/db_cleanup.sh, make it executable with chmod +x, and configure your system crontab to execute it on the half-hour boundary.
// Install: npm install node-cron
const cron = require('node-cron');
let isJobRunning = false;
// Schedule to run every 30 minutes (*/30 * * * *)
cron.schedule('*/30 * * * *', async () => {
if (isJobRunning) {
console.warn(`[${new Date().toISOString()}] Previous job execution is still in progress. Skipping execution.`);
return;
}
isJobRunning = true;
console.log(`[${new Date().toISOString()}] Initiating periodic cache sync job...`);
try {
// Simulate an asynchronous API synchronization task
await performCacheSync();
console.log(`[${new Date().toISOString()}] Cache sync completed successfully.`);
} catch (error) {
console.error(`[${new Date().toISOString()}] Error during cache sync:`, error);
} finally {
isJobRunning = false;
}
});
async function performCacheSync() {
return new Promise((resolve) => setTimeout(resolve, 5000));
} › Setup notes
Run this script as a daemon process using PM2 or systemd to ensure continuous execution of the thirty-minute interval scheduler.
# Install: pip install apscheduler
import logging
import time
from apscheduler.schedulers.blocking import BlockingScheduler
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
logger = logging.getLogger(__name__)
def run_api_polling():
logger.info("Starting scheduled API polling task...")
try:
# Simulate API call logic
time.sleep(2)
logger.info("API polling task completed successfully.")
except Exception as err:
logger.error(f"Execution failed: {err}", exc_info=True)
if __name__ == "__main__":
scheduler = BlockingScheduler()
# Standard cron expression equivalent: every 30 minutes
scheduler.add_job(
run_api_polling,
'cron',
minute='*/30',
id='api_polling_job',
coalesce=True,
max_instances=1
)
logger.info("Scheduler initialized. Running every 30 minutes.")
try:
scheduler.start()
except (KeyboardInterrupt, SystemExit):
logger.info("Scheduler stopped cleanly.") › Setup notes
Execute this Python script in your application environment. APScheduler will handle the parsing and execution based on system time.
package main
import (
"context"
"log"
"os"
"os/signal"
"sync/atomic"
"syscall"
"time"
"github.com/robfig/cron/v3"
)
type SafeJob struct {
running int32
}
func (j *SafeJob) Run() {
// Prevent concurrent execution using atomic operations
if !atomic.CompareAndSwapInt32(&j.running, 0, 1) {
log.Println("[Warning] Previous job is still active. Skipping this execution.")
return
}
defer atomic.StoreInt32(&j.running, 0)
log.Println("[Info] Starting health check verification...")
time.Sleep(3 * time.Second)
log.Println("[Info] Health check verification completed successfully.")
}
func main() {
// 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,
)))
job := &SafeJob{}
_, err := c.AddJob("*/30 * * * *", job)
if err != nil {
log.Fatalf("Failed to schedule job: %v", err)
}
c.Start()
log.Println("Cron engine started. Running job every 30 minutes.")
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
log.Println("Shutting down cron engine gracefully...")
c.Stop()
} › Setup notes
Build and run the compiled Go binary. It uses the robfig/cron library configured to parse standard 5-field cron definitions.
package com.cronbase.scheduler;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
@Component
public class HalfHourCleanupScheduler {
private static final Logger LOGGER = Logger.getLogger(HalfHourCleanupScheduler.class.getName());
private final AtomicBoolean isRunning = new AtomicBoolean(false);
// Spring's cron format accepts 6 fields (seconds, minutes, hours, day-of-month, month, day-of-week)
// "0 */30 * * * *" translates to the standard "*/30 * * * *" executing on the minute boundary
@Scheduled(cron = "0 */30 * * * *")
public void executeCleanup() {
if (!isRunning.compareAndSet(false, true)) {
LOGGER.warning("Previous execution is still running. Skipping current run.");
return;
}
LOGGER.info("Executing scheduled database cleanup...");
try {
// Simulate task execution
Thread.sleep(10000);
LOGGER.info("Cleanup successfully finished.");
} catch (InterruptedException e) {
LOGGER.severe("Task execution interrupted: " + e.getMessage());
Thread.currentThread().interrupt();
} finally {
isRunning.set(false);
}
}
} › Setup notes
Add @EnableScheduling to your main Spring Boot application configuration class to activate this scheduled component.
apiVersion: batch/v1
kind: CronJob
metadata:
name: app-cache-warmer
namespace: production
spec:
schedule: "*/30 * * * *"
# Forbid concurrent executions if previous run is still active
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
containers:
- name: cache-warmer
image: curlimages/curl:7.85.0
command:
- /bin/sh
- -c
- |
echo "Starting cache warming at $(date)"
curl -f -s https://api.internal/cache/warm || exit 1
echo "Cache warming complete."
resources:
limits:
cpu: "100m"
memory: "128Mi"
requests:
cpu: "50m"
memory: "64Mi"
restartPolicy: OnFailure › Setup notes
Apply this configuration using 'kubectl apply -f cronjob.yaml'. It ensures clean resource boundaries and prevents overlapping pods.
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(*/30 * * * ? *) Systemd Timer
OnCalendar*-*-* *:00/30:00
[Unit]
Description=Timer for cron expression: */30 * * * *
[Timer]
OnCalendar=*-*-* *:00/30:00
Persistent=true
[Install]
WantedBy=timers.target
Last verified:
Frequently Asked Questions
How do I prevent two instances of this job from running concurrently?
Implement a locking mechanism. In Kubernetes, set concurrencyPolicy to Forbid. In Linux shell scripts, use the flock utility to acquire an exclusive lock on a file descriptor before executing the main payload.
What happens to this schedule during Daylight Saving Time (DST) transitions?
Because this cron runs twice an hour, DST shifts do not affect the interval consistency. The job will continue to run every thirty minutes without interruption, though the absolute wall-clock hour will change.
Can I offset this job to run at 15 and 45 minutes past the hour instead?
Yes, you can modify the expression to '15,45 * * * *'. This is highly recommended in shared environments to avoid the resource spikes associated with running exactly on the hour.
How should I monitor a job that runs at this frequency?
Implement dead man's snitch monitoring (heartbeat alerts). Since the job runs regularly, set up an alert that triggers if no successful execution ping is received within 35 to 40 minutes.
Is this schedule suitable for heavy database migrations or backups?
No, a thirty-minute interval is generally too frequent for heavy operations. If a backup fails or hangs, it can quickly cascade and deplete database connections. Use daily or weekly schedules for intensive tasks.
* Explore
Related expressions you might need
Last verified: