Day-of-month AND day-of-week
Setting both fields means OR, not AND, so this runs far more often than intended
0 0 1 * 1
Paste a cron expression and get it in plain English, with the next six run times in UTC and in your own timezone. Plus the traps: the day-of-month and day-of-week OR rule, dates that can never occur, and the Kubernetes timezone default.
Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.
The answer appears here
Paste on the left and press Explain. Nothing leaves this tab.
Nothing else to flag.
No formatting problems, and nothing the rules object to. Worth remembering what that covers: this reads the file you pasted, not the account or cluster it will be applied to.
No finding matches that filter.
Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.
Setting both fields means OR, not AND, so this runs far more often than intended
0 0 1 * 1
What * * * * * actually costs on a CronJob with a slow container
* * * * *
These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.
When both are restricted, cron ORs them rather than ANDing. `0 0 1 * 1` runs on the 1st AND every Monday, not on Mondays that fall on the 1st.
Instead:Leave one field as * , or use two separate schedules.
Kubernetes CronJobs ran in the kube-controller-manager's timezone, usually UTC, until the timeZone field arrived in 1.27.
Instead:Set spec.timeZone on 1.27 or later, and write the schedule in UTC otherwise.
With the default concurrencyPolicy of Allow, runs overlap and pile up until the node runs out of resources.
Instead:Set concurrencyPolicy: Forbid, and make the interval longer than the worst-case run.
Reading a cron expression is a skill nobody keeps sharp, because you write one every few months and then trust it for years.
The expression in plain English, each field explained on its own line, then the next six fire times shown twice: in UTC, and in your browser's timezone. Seeing both side by side is the fastest way to catch a schedule written for local office hours that will run in the middle of the night.
When both are restricted, cron ORs them rather than ANDing them. 0 0 13 * 5 is not Friday the 13th, it is the 13th of every month and also every Friday. This surprises people who have used cron for years, and it is flagged whenever both fields are set.
Day 30 in February can never happen, so the job silently never runs and nothing reports it. Day 31 is subtler: it skips five months a year. Both are caught before you deploy rather than the following quarter when someone asks why the report is missing.
A CronJob schedule is interpreted in UTC unless spec.timeZone is set, which is available from 1.27 onwards. Six-field expressions with a seconds column, which Quartz and Spring accept, are rejected by Kubernetes or misread field by field. Both are called out.
Every minute is 1,440 pods a day for a CronJob, each with image pull, scheduling and teardown, which is enough to hit API server rate limits on a busy cluster. Scheduling exactly on the hour is noted too, because everyone does it and shared infrastructure feels the spike.
@daily, @hourly, @weekly, @monthly and @yearly are expanded and explained. They work in Kubernetes and are not universally supported elsewhere, so the equivalent five-field form is given to copy.
Five fields separated by spaces, each one a unit of time, read left to right from smallest to largest. The syntax came from Unix in the 1970s and has barely changed since, which is why it turns up unaltered in Kubernetes, GitHub Actions, Jenkins and every scheduler in between.
Each field says which values of that unit the job should run on. An asterisk means every value. A job runs when all five fields match the current time: with one exception, covered below, that is responsible for most cron surprises.
* * * * *
| | | | |
| | | | +---- day of week 0-6 (Sunday is 0, and 7 is also Sunday)
| | | +------- month 1-12
| | +---------- day of month 1-31
| +------------- hour 0-23
+---------------- minute 0-59 There is no syntax beyond these. Anything more complicated is expressed by combining them, or by writing two schedules, which is usually the clearer answer when an expression starts needing a comment.
* every value * * * * * every minute
5 exactly that 5 * * * * at :05 past every hour
9-17 a range 0 9-17 * * * hourly from 09:00 to 17:00
*/15 a step */15 * * * * :00 :15 :30 :45
1,15 a list 0 0 1,15 * * midnight on the 1st and 15th
a step counts from the start of its range, so 0-30/10 is 0, 10, 20, 30 Four of the five fields are combined with AND: the minute and the hour and the month all have to match. The two day fields are the exception. When both day-of-month and day-of-week are restricted, the job runs if either matches, and when only one is restricted the other is ignored. This is POSIX behaviour rather than a quirk of any one scheduler, and it is the reason the section below exists.
minute AND hour AND month AND (day-of-month OR day-of-week)
restricted means anything other than *
0 0 13 * * only day-of-month is set -> the 13th
0 0 * * 5 only day-of-week is set -> every Friday
0 0 13 * 5 both are set -> the 13th, and Fridays Classic Unix cron uses the machine's local timezone. A Kubernetes CronJob does not: it interprets the schedule in UTC unless you set spec.timeZone, which only arrived in Kubernetes 1.27. A schedule written for office hours therefore drifts by an hour twice a year under daylight saving, or is simply wrong all year round. That is why the runs above are shown in UTC first, with your browser's timezone beside them.
spec:
schedule: "0 9 * * 1-5"
timeZone: "Europe/London" # without this, 09:00 means 09:00 UTC Most implementations accept a handful of shorthands. They expand to ordinary expressions, so there is nothing you can say with them that you cannot say with five fields. The exception is @reboot, which is not a schedule at all and is not supported by Kubernetes.
@yearly, @annually 0 0 1 1 *
@monthly 0 0 1 * *
@weekly 0 0 * * 0
@daily, @midnight 0 0 * * *
@hourly 0 * * * *
@reboot on startup, not a time, not supported by Kubernetes The OR rule above, and what it costs in practice.
Write 0 0 13 * 5
and the obvious reading is "midnight on Friday the 13th". What it
actually means is "midnight on the 13th of every month, and also
midnight every Friday", which is roughly 64 runs a year instead of one
or two.
The rule is that when day-of-month and day-of-week are both restricted, cron treats them as OR rather than AND. When only one is restricted, the other is ignored entirely. It is documented, it is decades old, and it still catches people, because every other field in the expression composes the way you expect.
Cron cannot express the AND. Run it daily and exit early unless today is the day, which also gives you somewhere to log the decision.