The 15th of every month
Mid-month billing runs and reconciliation jobs: fires at 09:00 on the 15th every month, with no weekday restriction involved.
Last updated
In plain English
- Fires at 09:00 on the 15th of every month
- 12 runs per year
Why it’s written this way
Day-of-month is pinned to 15, month and day-of-week are left open, and the time fields put it at 09:00. Every month has a 15th, so unlike end-of-month schedules this one never needs a fallback date.
Leaving day-of-week as * matters here: if it were also restricted, say to weekdays, cron would fire on the 15th OR any weekday — almost certainly not the intended billing-day-only schedule.
Edge cases to know
- →The 15th lands on a different weekday every month; a job that assumes 'a business day' (wire transfers, say) needs its own weekend or holiday check — cron never shifts the date for you.
- →If billing depends on totals from earlier in the month, make sure whatever feeds this job has actually finished by 09:00 on the 15th — cron fires on schedule whether or not the data is ready.
- →Testing this in staging usually means faking the system clock rather than waiting a month; confirm the downstream job tolerates a mocked date before relying on the test.
Part by part
- 0 9 15
- Nine in the morning on the fifteenth. The day-of-month field is what makes this monthly, and it is the field that interacts badly with day-of-week.
- * *
- Month open so it repeats monthly, and day-of-week left as a wildcard — which here is load-bearing rather than incidental.
Common mistakes
0 9 15 * 1Restricting day-of-month and day-of-week together makes cron run when EITHER matches, so this fires on the 15th and on every Monday. It is the most consequential quirk in cron syntax.
0 9 15 * *0 9 15 * * for a mid-month payroll cutoffIt runs on the 15th whatever day that is, including Saturdays and Sundays. Anything with a business meaning needs the script to check and defer, since cron cannot express "the nearest working day".
0 9 15,30 * *A twice-monthly schedule that skips February entirely, since there is no 30th. Semi-monthly is one of several intervals cron simply cannot express correctly.
Questions
Why does adding a weekday break this?
Because when both day fields are restricted, cron ORs them rather than requiring both. A schedule intended to narrow to "the 15th, if it is a Monday" instead widens to "the 15th, plus every Monday".
How do I run twice a month reliably?
Use 1,15 rather than 15,30 — both dates exist in every month. Any date above 28 is absent from at least one month, and February drops three of them.
What if the 15th is a weekend?
It still runs. Have the script exit and reschedule, or use a scheduler with business-day support — cron's calendar knowledge stops at the fields themselves.
Is there a way to express the middle of the month?
Not as a concept, only as a date. Months vary in length, so "the middle" is 15 or 16 depending on the month, and cron has no arithmetic to decide which.