A Kubernetes CronJob runs a Job on a repeating schedule defined by a standard five-field cron expression placed in the schedule field of the manifest, and the controller-manager evaluates that expression in its configured time zone to decide when to spawn each new pod.

That one-line definition carries the entire timing model. Every other field on a CronJob, including concurrencyPolicy, startingDeadlineSeconds, successfulJobsHistoryLimit, failedJobsHistoryLimit, and jobTemplate, controls what happens when a scheduled run starts, but none of them decide when the run starts. The schedule string is the only place where timing lives, and getting it wrong is the most common cause of "my CronJob never runs" support tickets.

The format is identical to a Unix crontab entry: minute, hour, day of month, month, and day of week, separated by single spaces. Five whitespace-separated fields, no seconds, no year, no question marks. That identity with classic Unix cron is intentional. Kubernetes chose the standard format so anyone who has written a crontab(5) entry already knows most of the rules, and so the same expression can be copy-pasted between a development crontab and a production cluster without translation.

how to create a cron job in kubernetes
how to create a cron job in kubernetes

How Kubernetes Reads the Schedule Field

The Kubernetes API validates spec.schedule against the documented five-field grammar on every create and update. A pod template, restart policy, and other Job-shaped fields sit underneath, but the scheduler only inspects the string in spec.schedule. The controller-manager polls the schedule and, on a match, creates a new Job resource from spec.jobTemplate; that Job in turn creates one or more pods.

Because Kubernetes inherits the Unix crontab grammar, the same special characters apply. An asterisk (*) means every valid value in that field, a slash (/) introduces a step value, a hyphen (-) defines an inclusive range, and commas enumerate discrete values. A schedule of */5 * * * * therefore asks the controller to run every five minutes of every hour of every day; 0 9 * * 1-5 asks it to run at 09:00 on weekdays only. The grammar is specified in crontab(5) on man7.org and reproduced by cronie's crontab(5) reference, both of which Kubernetes aligns with.

Why Hand-Written Cron Expressions Break

Most failed CronJob schedules come from one of three mistakes: transposing two fields, misreading the day-of-week numbering, or accidentally setting both day-of-month and day-of-week when only one was intended. The standard field order, minute, hour, day of month, month, day of week, is easy to flip mentally because five positions blur together, and a swapped minute and hour pair silently moves the job to a wildly different time without raising a validation error.

Day of week follows Unix numbering, where Sunday is 0 and Saturday is 6, not the 1-to-7 numbering that some calendar libraries use. The combined-fields trap is the most subtle: when both day-of-month and day-of-week are restricted, classic cron uses OR semantics, so 0 9 1 * 1 runs on the first of every month and on every Monday, not on the first Monday only. Kubernetes preserves this behavior, which trips up readers who expect AND semantics.

Build the Schedule With the Cron Expression Generator

The Cron Expression Generator removes the memorization step entirely. You choose the frequency, adjust the controls that appear for that schedule, and copy the resulting five-field string. The relevant fields update immediately and a plain-English summary describes the same schedule in words, which makes it easy to double-check the meaning before the expression reaches a cluster.

  1. Pick the schedule type that matches the desired frequency: every minute, every N minutes, hourly, daily, weekdays, weekly, or monthly.
  2. Set the only control that schedule exposes, interval, minute, hour, weekday, or day of month, to the value you want.
  3. Read the generated five-field expression and the plain-English summary that appear below the controls.
  4. Copy the expression, then paste it into spec.schedule in the CronJob manifest.
  5. Verify the schedule by parsing it and listing the next run times before deploying the manifest.

Add the Schedule to a Kubernetes CronJob Manifest

With a valid expression in hand, the manifest itself is short. The following example runs a database backup container at 02:30 every night, using the daily preset of the generator to produce 30 2 * * *:

apiVersion: batch/v1 kind: CronJob metadata: name: db-backup spec: schedule: "30 2 * * *" concurrencyPolicy: Forbid startingDeadlineSeconds: 300 successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 1 jobTemplate: spec: template: spec: restartPolicy: OnFailure containers: - name: backup image: backup-tools:1.4 args: ["/usr/local/bin/backup.sh"]

Five concrete steps wire this into a cluster:

  1. Save the manifest as db-backup-cronjob.yaml in your working directory.
  2. Run kubectl apply -f db-backup-cronjob.yaml against the target cluster.
  3. Confirm registration with kubectl get cronjob db-backup; the SCHEDULE column should show the expression you pasted.
  4. Watch for the first scheduled run with kubectl get jobs --watch and follow the spawned Job with kubectl logs on its pods.
  5. Force a one-off run on demand with kubectl create job --from=cronjob/db-backup manual-1 to verify the template without waiting for the clock.

Cron Field Ranges and Allowed Values

The Kubernetes scheduler accepts exactly the values documented by crontab(5). Out-of-range numbers fail API validation on apply, which is a useful safety net, but staying inside the documented ranges from the start avoids round-trips through the API server:

FieldPositionValid rangeAllowed special syntax
Minute10-59*, */n, a-b, a,b,c
Hour20-23*, */n, a-b, a,b,c
Day of month31-31*, */n, a-b, a,b,c
Month41-12*, */n, a-b, a,b,c
Day of week50-6 (Sun-Sat)*, */n, a-b, a,b,c

Names such as MON or JAN are also accepted in the day-of-week and month fields, matching the crontab(5) reference. The generator keeps every input inside these ranges, so the expression it produces is guaranteed to pass Kubernetes validation.

Verify the Schedule Before It Reaches Production

A schedule that parses correctly can still mean the wrong thing. Two cheap verifications catch most surprises. First, paste the expression into the Cron Parser to see the next few match times. That immediately exposes a transposed field, an off-by-one weekday, or a missing step. Second, deploy to a non-production cluster and watch the first two or three runs land where you expect, with logs captured for review. For a deeper walkthrough of how to read the parsed output and confirm timing, the parse-and-preview guide covers the workflow end to end.

The controller-manager's time zone matters here. Kubernetes documents that CronJob schedules are evaluated against the --time-zone flag passed to kube-controller-manager, which defaults to UTC. A schedule that "feels like" 09:00 local time will fire at 09:00 UTC instead unless the controller is started with an explicit time zone, so confirm that flag in your cluster bootstrap before trusting wall-clock intuition. Daylight-saving transitions can also skip or repeat an hour of local wall-clock time, which is why UTC is the safer default for production.

Common Pitfalls in Kubernetes CronJob Schedules

Three traps catch experienced operators. First, monthly jobs scheduled for the 29th, 30th, or 31st simply do not run in February or in 30-day months that lack that date; the controller skips the month rather than rolling forward. Second, an expression that combines concurrencyPolicy: Allow with a busy schedule can pile up overlapping Jobs if a single run takes longer than the interval, so Forbid or Replace is usually the safer choice. Third, the expression only describes timing, not idempotency, retries, locking, or monitoring; those have to be designed into the container and the surrounding platform.

The generator sidesteps the first pitfall by exposing monthly schedules as a discrete preset that accepts any day from 1 to 31, and the field constraints documented above keep every input inside the range the controller accepts. The other two pitfalls are policy and application-design decisions that no schedule string can resolve, so they belong in the manifest's concurrencyPolicy, in startingDeadlineSeconds, and in the container's error-handling code rather than in the cron expression itself. The same caveat the generator makes for any cron use case applies here: confirm the target system accepts standard five-field cron before deploying, because some hosted schedulers add seconds, years, question marks, or vendor-specific syntax.

Putting It Together

Building a working Kubernetes CronJob is a three-step workflow once the schedule is decided: pick the frequency in the Cron Expression Generator, paste the resulting five-field string into spec.schedule, and verify the next run times against the controller-manager's time zone before relying on the job in production. Each step is small, the format is stable, and the validation is automatic; the only real failure mode is writing the wrong five fields by hand. Generation happens locally inside the browser, so the schedule and any command references never leave your machine until the manifest is applied with kubectl.

Related reading: Cron Parser Online: Validate Expressions and Next Run Times.