Run Monthly Jobs on the 15th at Midnight | CronBase
0 0 15 * * At midnight on the fifteenth day of each calendar month.
The `0 0 15 * *` cron expression schedules a task to execute precisely at midnight, or 12:00 AM, on the fifteenth day of every month. This specific mid-month cadence is highly effective for distributing heavy database processing, generating mid-period reports, and executing recurring monthly maintenance tasks outside of peak billing cycles.
- Minute
- 0
- Hour
- 0
- Day of Month
- 15
- Month
- *
- Day of Week
- *
Next 5 Runs
- in 20d 3h
- in 51d 3h
- in 81d 3h
- in 112d 3h
- in 142d 3h
* Tools
Code & Implementations
#!/usr/bin/env bash
# Production-ready crontab entry and execution wrapper
# Install: (crontab -l 2>/dev/null; echo "0 0 15 * * /usr/local/bin/mid_month_job.sh >> /var/log/cron_mid_month.log 2>&1") | crontab -
set -euo pipefail
LOCKFILE="/var/run/mid_month_job.lock"
exec 9>"$LOCKFILE"
if ! flock -n 9; then
echo "[ERROR] $(date): Job already running or locked." >&2
exit 1
fi
echo "[INFO] $(date): Starting monthly maintenance job..."
# Core business logic goes here
# /usr/local/bin/run-reconciliation.sh
echo "[INFO] $(date): Job completed successfully." › Setup notes
Save this script to /usr/local/bin/mid_month_job.sh, make it executable, and append the cron entry to your user's crontab using the provided installation command.
const cron = require('node-cron');
// Schedule to run at midnight on the 15th of every month
cron.schedule('0 0 15 * *', async () => {
console.log(`[${new Date().toISOString()}] Starting monthly data reconciliation...`);
try {
// Simulate processing logic
await performMonthlyReconciliation();
console.log(`[${new Date().toISOString()}] Monthly job executed successfully.`);
} catch (error) {
console.error(`[${new Date().toISOString()}] Critical error during monthly job:`, error);
// Integrate with paging/alerting system here (e.g., PagerDuty, Sentry)
}
}, {
scheduled: true,
timezone: "UTC"
});
async function performMonthlyReconciliation() {
return new Promise((resolve) => setTimeout(resolve, 5000));
} › Setup notes
Install node-cron using 'npm install node-cron', save this script, and run it as a background process or PM2 daemon.
import logging
from datetime import datetime
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
logger = logging.getLogger(__name__)
def run_monthly_task():
logger.info("Starting scheduled mid-month task...")
try:
# Core business logic here
logger.info("Mid-month task completed successfully.")
except Exception as e:
logger.error(f"Failed to execute mid-month task: {str(e)}", exc_info=True)
if __name__ == "__main__":
scheduler = BlockingScheduler()
# Standard cron 0 0 15 * * maps to: minute=0, hour=0, day=15, month=*, day_of_week=*
trigger = CronTrigger(minute=0, hour=0, day=15, timezone='UTC')
scheduler.add_job(run_monthly_task, trigger)
logger.info("Scheduler initialized. Waiting for execution on the 15th of the month...")
try:
scheduler.start()
except (KeyboardInterrupt, SystemExit):
logger.info("Scheduler stopped cleanly.") › Setup notes
Install APScheduler via 'pip install apscheduler' and run this script as a persistent service on your application server.
package main
import (
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/robfig/cron/v3"
)
func main() {
logger := log.New(os.Stdout, "[CRON] ", log.LstdFlags|log.Lshortfile)
utcLoc, err := time.LoadLocation("UTC")
if err != nil {
logger.Fatalf("Failed to load UTC timezone: %v", err)
}
c := cron.New(cron.WithLocation(utcLoc))
_, err = c.AddFunc("0 0 15 * *", func() {
logger.Println("Executing monthly maintenance job...")
if err := performJob(); err != nil {
logger.Printf("ERROR: Monthly job failed: %v", err)
} else {
logger.Println("Monthly job completed successfully.")
}
})
if err != nil {
logger.Fatalf("Error scheduling job: %v", err)
}
c.Start()
logger.Println("Scheduler started. Waiting for the 15th...")
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
logger.Println("Shutting down scheduler...")
c.Stop()
}
func performJob() error {
time.Sleep(2 * time.Second)
return nil
} › Setup notes
Initialize a Go module, pull the v3 cron dependency with 'go get github.com/robfig/cron/v3', and build/execute the binary.
package com.example.scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class MonthlyTaskScheduler {
private static final Logger logger = LoggerFactory.getLogger(MonthlyTaskScheduler.class);
// Spring uses a 6-field cron pattern (second, minute, hour, day, month, day-of-week).
// "0 0 0 15 * *" executes at midnight on the 15th day of every month.
@Scheduled(cron = "0 0 0 15 * *", zone = "UTC")
public void executeMidMonthJob() {
logger.info("Starting mid-month background processing...");
try {
processBillingReconciliation();
logger.info("Mid-month background processing finished successfully.");
} catch (Exception e) {
logger.error("Critical error during mid-month scheduled task execution", e);
}
}
private void processBillingReconciliation() throws Exception {
Thread.sleep(1000);
}
} › Setup notes
Ensure '@EnableScheduling' is declared on your Spring Boot application class, then register this Component in your package scan.
apiVersion: batch/v1
kind: CronJob
metadata:
name: mid-month-reconciliation
namespace: default
spec:
schedule: "0 0 15 * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5
startingDeadlineSeconds: 1800
jobTemplate:
spec:
template:
spec:
containers:
- name: worker
image: registry.example.com/jobs/reconciliation:v1.2.0
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "1Gi"
restartPolicy: OnFailure › Setup notes
Save this YAML configuration to 'cronjob.yaml' and apply it to your Kubernetes cluster using 'kubectl apply -f cronjob.yaml'.
Last verified:
Keep this cron job monitored 24/7
UptimeRobot alerts you the moment a scheduled job stops responding. Free plan monitors up to 50 endpoints — no credit card required.
Platform Equivalents
AWS EventBridge
Standard cron expressions often need conversion for AWS EventBridge schedules.
cron(0 0 15 * ? *) Systemd Timer
OnCalendar*-*-15 00:00:00
[Unit]
Description=Timer for cron expression: 0 0 15 * *
[Timer]
OnCalendar=*-*-15 00:00:00
Persistent=true
[Install]
WantedBy=timers.target
Last verified:
Frequently Asked Questions
How do I handle daylight saving time changes at midnight?
To prevent duplicate executions or skipped runs during seasonal clock shifts, always configure your cron daemon or scheduler to run in Coordinated Universal Time (UTC) rather than local system time.
What happens if the scheduled mid-month job fails?
Because this job only runs once a month, manual intervention is usually required. Implement robust error catching to dispatch immediate alerts to Slack or PagerDuty, and ensure your script is fully idempotent so it can be safely re-run manually if it fails midway.
Why run heavy tasks on the 15th instead of the 1st?
The 1st and last days of the month are notorious for peak resource usage due to standard accounting, invoicing, and reporting runs. Scheduling on the 15th mitigates database contention by shifting heavy, non-time-critical processing to a quieter period.
How can I test this monthly job without waiting for the 15th?
You should separate your execution logic from the scheduling framework. Build your task so it can be run via an on-demand command-line flag, API endpoint, or manually triggered Kubernetes Job alongside your automated cron schedule.
Is there a risk of overlapping executions for this schedule?
With a monthly frequency, overlap is highly unlikely unless the task hangs indefinitely. Use process locks (like `flock` in Bash) or set `concurrencyPolicy: Forbid` in Kubernetes to prevent a stuck job from colliding with the next month's execution.
* Explore
Related expressions you might need
Last verified: