Cron on the 31st: 0 0 31 * * only fires seven times a year
0 0 31 * * fires only in January, March, May, July, August, October and December, seven times a year. In the other five months the date does not exist and the job simply does not run.
At a glance
- Expression
- 0 0 31 * *
- Fires
- 7 times a year. Day 30 gives 11 and day 29 gives 11 or 12.
- Restricted fields
- Day of month pinned to 31, minute and hour pinned. Month and day of week open.
- Equivalent macro
- None.
- Months that have a 31st
- January, March, May, July, August, October, December.
- Day 30 and day 29
- Day 30 fires 11 times a year. Day 29 fires 11 times, or 12 in a leap year.
In detail
Cron matches a date against a pattern, and a date that does not exist can never match. There is no 31 February, so the expression is not an error and the job is not deferred: February simply passes without a run. The same is true of April, June, September and November, which leaves seven months a year.
This is the single most common way a monthly job silently loses runs. The crontab looks right, the expression parses, the job works every time it runs, and five months a year nothing happens. Because there is no error and no log line, the gap is usually discovered by somebody asking why a report is missing rather than by any monitoring.
Day 30 is a milder version of the same problem: it exists in eleven months and never in February, so a monthly job pinned to the 30th misses one run a year. Day 29 exists in eleven months plus February in a leap year, so it misses one run in three years out of four.
If what you wanted was the end of the month, none of these is the right expression, because the end of the month is a different date in different months. The workable approach is to schedule the job across the possible last days and let the script check whether tomorrow is the first, which is covered on its own page.
Examples
0 0 31 * *Seven runs a year. February, April, June, September and November are skipped entirely.
0 0 28 * *Twelve runs a year, in every month including February.
0 0 29 2 *Fires in leap years only, roughly once every four years.
Things that catch people out
- The job does not run late in February. It does not run at all, and nothing reports the fact.
- Day 29, 30 and 31 all lose runs. Only days 1 to 28 are guaranteed to exist in every month.
- Cron never rolls a missed date forward. There is no catch up behaviour for a date that did not occur.
Try it: Cron Expression Parser
Paste any five or six field expression to get a plain English description, a per field breakdown and the next run times in your local time.
Cron Expression Parser
LOCAL · Browser-OnlyParse cron expressions and see the next scheduled run times
Supports 5-field (minute hour day month weekday), 6-field (with seconds), named values (JAN, MON), and @macros (@daily, @hourly, ...)
Use the dropdowns below to build your cron expression visually, or type a custom value in each field.
Human-Readable Description
Every 5 minutes
Field Breakdown
| Field | Allowed | Value | Meaning |
|---|---|---|---|
| Minute | 0-59 | */5 | every 5 minute(s) |
| Hour | 0-23 | * | every hour |
| Day of Month | 1-31 | * | every day of month |
| Month | 1-12 | * | every month |
| Day of Week | 0-7 | * | every day of week |
Related cron expressions
All cron expressions →Cron on the last day of the month: the L workaround
This parser rejects the Quartz L character, so the last day of the month needs a date range plus a script guard that checks whether tomorrow is the 1st.
Cron on 29 February: 0 0 29 2 * and the leap year rule
0 0 29 2 * runs only when 29 February exists, roughly once every four years. The century rule means 2100 has no leap day at all.
The cron day of month field: 1 to 31 and missing dates
The day of month field runs 1 to 31, and a date that does not exist in a given month simply never matches. Steps start at 1, giving odd days.
Cron on the first day of the month: 0 0 1 * *
0 0 1 * * runs at midnight on the 1st of every month, twelve times a year, and is exactly what the @monthly macro expands to.
Cron on the first Monday of the month: the OR rule trap
0 9 1-7 * 1 does not mean the first Monday. Because both date fields are restricted, it fires on days 1 to 7 and on every Monday as well.
Cron on the last Friday of the month: no # or L support
The Quartz forms 6L and 6#5 are rejected here. Schedule every Friday and let the script check whether the following Friday is in the next month.
Tools for this
- Cron Expression Parser Free online cron expression parser - parse cron expressions and see the next scheduled run times
- Crontab Generator Free online crontab generator - build cron expressions visually with an intuitive interface