Run a Cron Job on the Last Day of the Month
Cron has no last-day-of-month field. Run on days 28 to 31 and test in the command that tomorrow is the first, escaping the percent sign in date.
A report must run on the last day of every month, and months have 28, 29, 30 or 31 days.
for d in 2026-02-27 2026-02-28 2026-03-30 2026-03-31 2028-02-28 2028-02-29; do
if [ "$(date -d "$d + 1 day" +%d)" = 01 ]; then echo "$d: last day, run"; else echo "$d: skip"; fi
done
echo ---
echo '59 23 28-31 * * [ "$(date -d tomorrow +\%d)" = 01 ] && /usr/local/bin/month-end.sh' | crontab -
crontab -l
crontab -r2026-02-27: skip
2026-02-28: last day, run
2026-03-30: skip
2026-03-31: last day, run
2028-02-28: skip
2028-02-29: last day, run
---
59 23 28-31 * * [ "$(date -d tomorrow +\%d)" = 01 ] && /usr/local/bin/month-end.shStandard cron cannot say "the last day", so the job is scheduled for days 28 to 31 and a test at the start of the command lets it through only when tomorrow's day of the month is 01. The loop checks the logic against month ends, including February in a leap year. In a crontab the percent sign ends the command, so it must be escaped with a backslash, as crontab(5) says, which is what the second block does.
date -d is GNU; BSD and macOS date use -v+1d instead. Cron runs the line with /bin/sh, so the test uses only [ ] and no Bash features. When the day-of-month and day-of-week fields are both restricted, cron runs the job when either one matches, which is why the weekday field is left as a star. Some cron implementations support L in the day field, and Vixie cron does not.