Run Jobs on the Last Day of the Month | CronBase
0 0 L * * At midnight on the last day of every month
The `0 0 L * *` cron schedule executes a task once a month at midnight on the very last day of the month, regardless of whether that month has twenty-eight, twenty-nine, thirty, or thirty-one days. It is ideal for monthly billing cycles, data archiving, and generating financial reports.
- Minute
- 0
- Hour
- 0
- Day of Month
- L
- Month
- *
- Day of Week
- *
* Tools
Code & Implementations
#!/usr/bin/env bash
# Since standard Linux cron doesn't natively support 'L',
# schedule this script to run daily (0 0 28-31 * *), and
# this guard clause will ensure the payload only runs on the last day.
set -euo pipefail
TOMORROW=$(date -d "tomorrow" +%d)
if [ "$TOMORROW" -ne 1 ]; then
echo "Today is not the last day of the month. Exiting."
exit 0
fi
echo "Starting end-of-month maintenance tasks..."
# Call your production payload here
# /opt/bin/run-billing-aggregation.sh › Setup notes
Save this script as run-monthly.sh, make it executable with chmod +x, and configure it to run daily at midnight (0 0 28-31 * *) in your system crontab.
// Node.js implementation using standard cron pattern.
// Since many Node libraries lack native 'L' support, we schedule
// daily for the end of the month and evaluate programmatically.
const cron = require('node-cron');
function isLastDayOfMonth() {
const today = new Date();
const tomorrow = new Date(today);
tomorrow.setDate(today.getDate() + 1);
return tomorrow.getDate() === 1;
}
// Runs daily at midnight from the 28th to the 31st
cron.schedule('0 0 28-31 * *', () => {
if (!isLastDayOfMonth()) {
return;
}
console.log('Executing end-of-month database reconciliation...');
try {
// Execute production business logic here
} catch (error) {
console.error('Failed to run monthly job:', error);
}
}); › Setup notes
Install node-cron using npm install node-cron, then run this script as a persistent background process using PM2 or Docker.
# Python script designed to run daily on days 28-31.
# It checks if today is the last day of the month before executing.
import datetime
import sys
def is_last_day_of_month():
today = datetime.date.today()
tomorrow = today + datetime.timedelta(days=1)
return tomorrow.day == 1
def run_monthly_pipeline():
if not is_last_day_of_month():
print("Skipping: Today is not the last day of the month.")
sys.exit(0)
print("Starting monthly data pipeline...")
try:
# Insert production database aggregation code here
pass
except Exception as e:
print(f"Error executing monthly pipeline: {e}")
sys.exit(1)
if __name__ == "__main__":
run_monthly_pipeline() › Setup notes
Schedule this script using standard crontab with '0 0 28-31 * *' to run daily during the end-of-month window.
package main
import (
"fmt"
"time"
)
// IsLastDayOfMonth checks if the current time is on the last day of its month.
func IsLastDayOfMonth(t time.Time) bool {
tomorrow := t.AddDate(0, 0, 1)
return tomorrow.Day() == 1
}
func main() {
// In Go, standard cron libraries like robfig/cron require manual guards
// if scheduled with generic patterns, or custom Spec parsers.
now := time.Now().UTC()
if !IsLastDayOfMonth(now) {
fmt.Println("Not the last day of the month. Skipping execution.")
return
}
fmt.Println("Executing monthly ledger closeout process...")
// Place production execution logic here
} › Setup notes
Compile the binary and schedule it to run daily at midnight during the end-of-month dates (28-31) using any standard system scheduler.
import org.quartz.CronScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.JobDetail;
import org.quartz.JobBuilder;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
// Quartz natively supports the 'L' character for Last Day of Month.
public class MonthlyBillingJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("Executing Quartz monthly billing run...");
// Implement production billing logic here
}
public static void main(String[] args) {
JobDetail job = JobBuilder.newJob(MonthlyBillingJob.class)
.withIdentity("monthlyBillingJob", "group1")
.build();
// "0 0 0 L * ?" is Quartz syntax for midnight on the last day of the month
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("monthlyBillingTrigger", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0 0 0 L * ?"))
.build();
}
} › Setup notes
Include the Quartz Scheduler dependency in your Maven/Gradle project and run this class within your application container.
apiVersion: batch/v1
kind: CronJob
metadata:
name: monthly-cleanup-job
namespace: production
spec:
# Kubernetes CronJob uses standard Go cron which does not support 'L'.
# We schedule for midnight on days 28-31 and use a shell wrapper to check.
schedule: "0 0 28-31 * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5
jobTemplate:
spec:
template:
spec:
containers:
- name: worker
image: alpine:3.18
command:
- /bin/sh
- -c
- |
TOMORROW=$(date -d "tomorrow" +%d)
if [ "$TOMORROW" -ne 1 ]; then
echo "Not the last day of the month. Exiting."
exit 0
fi
echo "Starting monthly maintenance..."
# Run production payload here
restartPolicy: OnFailure › Setup notes
Apply this manifest to your cluster using kubectl apply -f cronjob.yaml to safely run end-of-month jobs.
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
Systemd Timer
OnCalendar*-*-* 00:00:00
[Unit]
Description=Timer for cron expression: 0 0 L * *
[Timer]
OnCalendar=*-*-* 00:00:00
Persistent=true
[Install]
WantedBy=timers.target
Last verified:
Frequently Asked Questions
Does standard Linux crontab natively support the 'L' character?
No, standard Vixie cron and standard Linux crontabs do not natively support the 'L' character. Schedulers like Quartz, AWS EventBridge, and systemd-timers support it, but for standard crontab, you must run a daily script at month-end and check if tomorrow is the first.
How does this expression handle leap years in February?
If the underlying scheduler natively supports the 'L' character, it dynamically calculates leap years, running on February 29th during a leap year and February 28th otherwise. If using a daily fallback wrapper, the date utility calculates this automatically.
What timezone should I use when scheduling end-of-month jobs?
Always configure your cron daemon or orchestrator to run in Coordinated Universal Time (UTC). Using local timezones can cause your job to run twice or be skipped entirely during Daylight Saving Time (DST) transitions at month-end.
How can I prevent database locking during heavy monthly aggregations?
Avoid running heavy queries directly on your primary transactional database. Offload the data to a read-replica, use batching with small limit/offset chunks, or stream the data to an asynchronous processing queue to prevent performance degradation.
What is the best way to test a job scheduled with 'L'?
Since 'L' only occurs once a month, test your script logic by temporarily changing the cron schedule to run every minute in a staging environment, or mock the system clock in your application code to verify the date-evaluation logic.
* Explore
Related expressions you might need
Last verified: