Skip to content
cronhelp.me
Guide · Foot-guns

Day of month that some months never reach

Short answer
A schedule pinned to the 29th, 30th or 31st silently skips the months that are shorter. Day 31 runs in only seven months of the year.
Wrong"the last day of every month"
0 0 31 * *
Rightuse a day every month has, or your dialect's L token
0 0 28 * *

Not every month has 29, 30 or 31 days, so a schedule pinned to one of those dates simply does not fire in the months that fall short. This is usually a surprise, because people reach for 31 meaning "the end of the month" and get "the 31st, when there is one" instead.

The 31st exists in only seven months — January, March, May, July, August, October and December — so 0 0 31 * * runs seven times a year, skipping February, April, June, September and November entirely. The 30th skips February. The 29th runs in every month except February in common years, and in February only on leap years.

This warning lists which months a given day never reaches. It is distinct from never-fires: there, no selected month has the day and the job never runs at all, which is an error. Here the job does run, just not as often as intended, which is a warning.

The fix depends on what you actually meant. If you wanted a specific date that every month has, pick 28 or lower — 0 0 28 * * runs every month without exception. If you genuinely meant "the last day of the month", standard cron cannot express it: some dialects (Quartz, and AWS EventBridge) provide an L token for exactly this, so 0 0 L * * is the last day where supported. Where it is not, run the job daily and exit early unless tomorrow is the 1st: [ "$(date -d tomorrow +\%d)" = 01 ] || exit 0. That guard fires on the true last day of every month, long or short.

Watch the linter flag it live — the finding below appears the moment the expression is entered:

Cron expression5-field
At 12:00 AM, on day 31 of the month
Lint
Warningdom-unreachable-months
This job silently skips February, April, June, September and November: the earliest day this schedule uses is the 31st, and those months never go that high. Cron does not adjust for month length — a date that does not exist simply never runs, with no error. Use a day every chosen month has (the 28th or lower always works).
Learn more →
Next runs
#
1
2
3
4
5
6
7
Copy as
All lint rulesSeverity: Warning