Skip to content
cron.surf

Run a Kubernetes CronJob on the last day of every month

Kubernetes has no exact expression for “Last day of the month”. Here is why, a workaround, and the platforms that can do it. A Kubernetes CronJob reads five-field cron in spec.schedule and starts a Job for each run.

Why Kubernetes can’t do this exactly

Defined on Quartz as 0 0 0 L * ? · “At 00:00, on the last day of the month”

  • day of month: Kubernetes has no “last day of the month”. Months end on the 28th to the 31st, so no single day matches.

    WorkaroundRun on days 28-31 and exit unless tomorrow is the 1st. Needs GNU date; in crontab, % is escaped as \%.
    0 0 28-31 * *  # job: [ "$(date -d tomorrow +%d)" = "01" ] || exit 0
    Not equivalent on its own: it needs a check inside the job.

Kubernetes gotchas

Day of week is 0-6The schedule parser stops at 6. Write 0 for Sunday; 7 is an error.
Too many missed startsIf a CronJob misses more than 100 starts, the controller stops starting it. Set startingDeadlineSeconds so old misses stop counting.
Overlapping runsconcurrencyPolicy defaults to Allow, so a slow Job can overlap the next one. Use Forbid or Replace.
*/n in a day field counts as setOnly * or ? leave a day field open. 0 0 */2 * 1 runs on odd days or on Mondays, while Vixie cron reads it as odd days that are Mondays.

FAQ

What is the Kubernetes cron expression for “Last day of the month”?

There is none that matches exactly. Kubernetes has no “last day of the month”. Months end on the 28th to the 31st, so no single day matches.

Which time zone does a Kubernetes schedule use?

Without spec.timeZone, in the local zone of kube-controller-manager, which is usually UTC. Set spec.timeZone (stable since Kubernetes 1.27) to an IANA name. CRON_TZ or TZ inside schedule is rejected.

Does a CronJob support @daily?

Yes. @yearly, @monthly, @weekly, @daily and @hourly work in spec.schedule.

Why did my CronJob run twice, or not at all?

A CronJob creates about one Job per scheduled time, not exactly one. Make the Job safe to repeat, and check startingDeadlineSeconds and concurrencyPolicy.