Run Monthly System Tasks at Midnight | CronBase
0 0 1 * * At midnight on the first day of every month
The `0 0 1 * *` cron expression schedules a job to run exactly at midnight (00:00) on the first day of every calendar month. This highly predictable, low-frequency interval is commonly used in enterprise environments for processing monthly billing cycles, generating financial reports, archiving historical databases, and initiating full backup routines.
- Minute
- 0
- Hour
- 0
- Day of Month
- 1
- Month
- *
- Day of Week
- *
Next 5 Runs
- in 6d 3h
- in 37d 3h
- in 67d 3h
- in 98d 3h
- in 128d 3h
* Tools
Code & Implementations
#!/usr/bin/env bash
# Ensure the target script exists and is executable
CLEANUP_SCRIPT="/usr/local/bin/monthly_cleanup.sh"
if [ -f "$CLEANUP_SCRIPT" ]; then
# Append the monthly cron job to the current user's crontab safely
(crontab -l 2>/dev/null; echo "0 0 1 * * $CLEANUP_SCRIPT >> /var/log/monthly_cleanup.log 2>&1") | crontab -
echo "Successfully scheduled monthly cleanup job."
else
echo "Error: Target script $CLEANUP_SCRIPT not found. Cron job installation aborted." >&2
exit 1
fi › Setup notes
Save this script as setup_cron.sh, make it executable with 'chmod +x setup_cron.sh', and run it. It registers the monthly cleanup script to execute at midnight on the 1st of every month, routing stdout and stderr to a dedicated log file.
const cron = require('node-cron');
const winston = require('winston');
// Configure a production-ready logger
const logger = winston.createLogger({
level: 'info',
transports: [new winston.transports.Console()]
});
// Schedule the task to run on the first day of every month at midnight in UTC
cron.schedule('0 0 1 * *', async () => {
logger.info('Starting scheduled monthly billing processing...');
try {
await runMonthlyBilling();
logger.info('Monthly billing completed successfully.');
} catch (error) {
logger.error('Critical failure during monthly billing execution:', error);
// Implement paging or alerting system calls here
}
}, {
scheduled: true,
timezone: "UTC"
});
async function runMonthlyBilling() {
// Business logic for processing billing
return Promise.resolve();
} › Setup notes
Install 'node-cron' and 'winston' using npm. Run this script as a daemon process (e.g., using PM2) to ensure continuous monitoring and monthly execution.
import logging
import sys
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('monthly_scheduler')
def execute_monthly_report():
logger.info("Starting monthly report generation...")
try:
# Simulating reporting job
logger.info("Monthly report generated successfully.")
except Exception as e:
logger.error(f"Failed to generate monthly report: {e}", exc_info=True)
if __name__ == "__main__":
scheduler = BlockingScheduler()
trigger = CronTrigger(day=1, hour=0, minute=0, timezone='UTC')
scheduler.add_job(execute_monthly_report, trigger=trigger, id='monthly_report_job')
try:
logger.info("Starting blocking scheduler for monthly tasks...")
scheduler.start()
except (KeyboardInterrupt, SystemExit):
logger.info("Scheduler stopped.")
sys.exit(0) › Setup notes
Install APScheduler using 'pip install apscheduler'. Run this script in a systemd service or container environment to ensure high-availability execution.
package main
import (
"log"
"time"
"github.com/robfig/cron/v3"
)
func main() {
// Use UTC to prevent issues with Daylight Saving Time transitions
loc, err := time.LoadLocation("UTC")
if err != nil {
log.Fatalf("Failed to load UTC timezone: %v", err)
}
c := cron.New(cron.WithLocation(loc))
_, err = c.AddFunc("0 0 1 * *", func() {
log.Println("Monthly backup sequence initiated...")
if err := performBackup(); err != nil {
log.Printf("CRITICAL ERROR: Monthly backup failed: %v", err)
} else {
log.Println("Monthly backup completed successfully.")
}
})
if err != nil {
log.Fatalf("Failed to schedule monthly job: %v", err)
}
c.Start()
// Keep the process alive indefinitely
select {}
}
func performBackup() error {
// Real backup logic goes here
return nil
} › Setup notes
Initialize a Go module, run 'go get github.com/robfig/cron/v3', and build the binary. Ensure your host system timezone matches expectations, though the code explicitly overrides to UTC.
package com.example.cron;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class MonthlyDataArchiver {
private static final Logger logger = LoggerFactory.getLogger(MonthlyDataArchiver.class);
// Standard 6-field Spring cron format: second, minute, hour, day, month, day-of-week
@Scheduled(cron = "0 0 0 1 * *", zone = "UTC")
public void archiveOldData() {
logger.info("Monthly archival process started.");
try {
executeArchival();
logger.info("Monthly archival completed successfully.");
} catch (Exception e) {
logger.error("CRITICAL: Monthly archival job failed!", e);
}
}
private void executeArchival() throws Exception {
// Perform data archival operations
}
} › Setup notes
Add '@EnableScheduling' to your Spring Boot application class. Ensure this bean is scanned, and Spring will manage the execution lifecycle using its internal thread pool.
apiVersion: batch/v1
kind: CronJob
metadata:
name: monthly-database-pruner
namespace: production
spec:
schedule: "0 0 1 * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5
jobTemplate:
spec:
template:
spec:
containers:
- name: db-pruner
image: postgres:15-alpine
command:
- /bin/sh
- -c
- "psql $DATABASE_URL -c 'VACUUM ANALYZE;'"
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secrets
key: url
restartPolicy: OnFailure › Setup notes
Apply this manifest using 'kubectl apply -f cronjob.yaml'. This definition ensures that if a previous monthly run is still active, new instances will be blocked ('Forbid') to avoid DB locks.
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 0 1 * ? *) Systemd Timer
OnCalendar*-*-01 00:00:00
[Unit]
Description=Timer for cron expression: 0 0 1 * *
[Timer]
OnCalendar=*-*-01 00:00:00
Persistent=true
[Install]
WantedBy=timers.target
Last verified:
Frequently Asked Questions
What happens if my server is offline at midnight on the first of the month?
Standard cron daemons will skip the execution entirely. To ensure the job runs when the system recovers, you should use anacron on Linux or configure a persistent queue/orchestrator (like Kubernetes with retry mechanisms or Airflow) that handles missed schedules.
How does Daylight Saving Time affect this monthly schedule?
If your server is set to a local timezone that observes DST, the midnight transition on the first of the month could occur twice (when falling back) or be skipped (when springing forward). To prevent this, always run cron jobs in UTC.
Can I run this job on the last day of the month instead of the first?
Standard cron does not easily support 'last day of the month' because months have varying lengths. You can either use a wrapper script that checks if tomorrow is the first, or use advanced dialects like Quartz or systemd timers that support the 'L' character (e.g., `0 0 L * *`).
How can I safely test a monthly cron job without waiting a full month?
You should separate your job execution logic from the scheduling mechanism. Write your script or application code to be triggerable via an HTTP endpoint, CLI command, or manual Kubernetes Job run, allowing you to test the execution on demand.
How do I handle overlapping executions if the monthly job takes over 24 hours?
To prevent overlapping runs, use lockfiles (like 'flock' in Bash), set 'concurrencyPolicy: Forbid' in Kubernetes, or implement a distributed lock pattern using Redis or database flags in your application code.
* Explore
Related expressions you might need
Last verified: