When setting up cron jobs, many developers assume that specifying "31" in the day-of-month field will automatically execute the job on the last day of every month. However, this is not how cron implementations actually work across Unix-like systems.
Most cron implementations follow these rules for the day-of-month field:
- If a month has fewer than 31 days, the job won't run at all that month
- The scheduler doesn't automatically adjust to the last valid day of the month
- This behavior is consistent across vixie-cron, cronie, and other common implementations
Consider this cron entry that runs on the 31st of every month:
0 0 31 * * /path/to/monthly_report.sh
Here's what actually happens:
January: Runs on 31st
February: Doesn't run
March: Runs on 31st
April: Doesn't run
May: Runs on 31st
June: Doesn't run
July: Runs on 31st
August: Runs on 31st
September: Doesn't run
October: Runs on 31st
November: Doesn't run
December: Runs on 31st
For true end-of-month execution, consider these approaches:
Option 1: Using L (Linux-specific)
Some modern cron implementations support the 'L' specifier:
0 0 L * * /path/to/script.sh
Option 2: Multiple Day Specification
Target the valid last days of all months:
0 0 28-31 * * [ $(date +\%d -d tomorrow) = 01 ] && /path/to/script.sh
Option 3: First-Day-of-Next-Month Approach
Run at midnight on the 1st and check if yesterday was month-end:
0 0 1 * * [ $(date +\%d -d yesterday) = $(date +\%d -d "$(date +\%Y-\%m-01) + 1 month - 1 day") ] && /path/to/script.sh
- The behavior might vary slightly between cron implementations
- Some systems (like AWS Cron) handle this differently
- Always test your cron jobs across month boundaries
- Consider adding logging to verify execution dates
For production systems requiring precise end-of-month execution, consider:
# Run daily but only process on month-end
0 0 * * * [ $(date +\%d -d tomorrow) = 01 ] && /path/to/end_of_month_task.sh
When configuring cron jobs with 31
as the day-of-month parameter, many developers assume it will automatically adjust for shorter months. However, cron's behavior is more literal than intuitive:
# This will NOT run on Feb 28/29, Apr 30, etc.
0 0 31 * * /path/to/monthly_script.sh
Through testing various cron implementations (Vixie cron, systemd, etc.), we observe:
- Jobs set to 31st only trigger when the month contains 31 days
- No automatic fallback to last day exists in standard cron
- The execution depends entirely on the system's cron daemon
For true month-end execution regardless of date, consider these approaches:
Option 1: Dual Cron Entries
# Regular 31-day months
0 0 31 * * [ $(date +\%m) -ne 2 ] && /path/to/script
# Special handling for February
0 0 28 2 * /path/to/script
Option 2: Next-Day Check
# Runs daily but only executes if tomorrow is month 1
0 0 * * * [ $(date -d tomorrow +\%d) -eq 1 ] && /path/to/script
Option 3: Using Modified Wrapper Script
#!/bin/bash
current_day=$(date +\%d)
last_day=$(date -d "$(date +\%Y\%m01) +1 month -1 day" +\%d)
if [[ "$current_day" == "$last_day" ]]; then
# Your month-end logic here
fi
Implementation varies slightly across platforms:
System | Handling of Invalid Dates |
---|---|
Vixie cron | Silently skips |
systemd timers | May log warnings |
Amazon EventBridge | Allows L for last day |