Can Cron Run on the Last Day of the Month?
Standard cron can't express “the last day of the month”: five-field cron has no L symbol, and a fixed day like 28, 30 or 31 is wrong for some months. The usual fix is to run on days 28 to 31 and let the job check whether tomorrow is the 1st.
Standard cron can't express “last day of the month” — months end on the 28th, 29th, 30th or 31st, and a fixed day-of-month can't follow that. A common workaround is 0 0 28-31 * * with a check in your script that tomorrow is the 1st.
— — — — —This isn't a mistake in what you typed — standard cron has no way to write this schedule. See the workarounds.
Fix the expression to see run times.
Why standard cron can't do this
Months end on the 28th, 29th, 30th or 31st, but the day of month field only takes fixed numbers, ranges, lists and steps. None of those can mean “whichever day is last”, so no single standard cron expression runs on the last day of every month.
Picking a fixed day always fails somewhere. 0 0 31 * * runs in only seven months and silently skips February, April, June, September and November. 0 0 28 * * runs every month, but up to three days early — fine for a reminder, wrong for anything that should include the month's final days of data.
Some schedulers do have a symbol for it. Quartz and AWS EventBridge accept L in the day of month field, meaning the last day of the month. System cron, Kubernetes CronJobs and GitHub Actions don't, and crontab refuses to install a line that contains it.
It's also worth asking what the job actually needs. Month-end work is usually about closing the month that's ending, and that's often easier a minute later — at 00:00 on the 1st, when the whole month is complete and 0 0 1 * * expresses the schedule perfectly.
Workarounds that do work
Run on days 28–31 and check the date
Schedule the job for every candidate day and let it continue only when tomorrow is the 1st. It runs at the right time in every month, including February in leap years. Remember that % must be escaped as \% inside a crontab line.
# GNU date (most Linux distributions)
59 23 28-31 * * [ "$(date -d tomorrow +\%d)" = "01" ] && /usr/local/bin/month-end.sh
# BSD / macOS date
59 23 28-31 * * [ "$(date -v+1d +\%d)" = "01" ] && /usr/local/bin/month-end.shRun at the start of the next month
If the job closes out a month, running it at 00:00 on the 1st gives it the complete month's data and needs no date logic at all. Just have the job work on the previous month rather than the current one.
0 0 1 * * /usr/local/bin/close-previous-month.shUse a scheduler that supports L
If the job already runs in Quartz or AWS EventBridge, use their last-day syntax instead. These expressions have six or seven fields, so they don't work in a system crontab.
# Quartz: seconds minutes hours day-of-month month day-of-week
0 59 23 L * ?
# AWS EventBridge: minutes hours day-of-month month day-of-week year
cron(59 23 L * ? *)Frequently asked questions
Does cron support L for the last day of the month?
Standard cron doesn't. L is an extension found in Quartz, AWS EventBridge and a few other schedulers; Vixie cron, cronie, Kubernetes CronJobs and GitHub Actions don't accept it.
Why not just use 0 0 28-31 * *?
Because it runs on every one of those days that exists in the month — four times in a 31-day month. It's only useful combined with a check that tomorrow is the 1st.
How do I run a job on the last weekday of the month?
Schedule 0 18 26-31 * * — without 1-5 in the day of week field, which would trigger cron's OR rule — and let the command continue only on a weekday when either tomorrow is the 1st, or today is a Friday and the 1st falls on the weekend or the Monday after.