KubernetesXIX · Jobs and CronJobsJobs and CronJobs
CronJobs — schedules, concurrency policy, and missed-run handling
What you'll learn
- Configure CronJob schedule syntax (cron format)
- Distinguish `concurrencyPolicy: Allow`, `Forbid`, and `Replace`
- Reason about `startingDeadlineSeconds` and missed-run handling
- Avoid the common production mistake: silent schedule failures
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
A CronJob is the controller for scheduled batch work.
It creates a Job at the schedule’s next firing time and
manages the Job’s lifecycle. The CronJob controller handles
three concerns plain Jobs do not: the schedule itself, the
behaviour when the previous Job is still running, and the
behaviour when a schedule is missed (controller down, clock
drift, paused cluster). This lesson covers each.
What a CronJob is
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-cleanup
spec:
schedule: "0 2 * * *" # 02:00 every day
timeZone: "Etc/UTC"
startingDeadlineSeconds: 300
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
suspend: false
jobTemplate:
spec:
backoffLimit: 4
activeDeadlineSeconds: 7200
ttlSecondsAfterFinished: 86400
template:
spec:
restartPolicy: OnFailure
containers:
- name: cleanup
image: cleanup:v1
The CronJob controller evaluates the schedule. At each
firing time, the controller creates a Job from
jobTemplate. The Job’s Pods run to completion; the Job is
deleted after ttlSecondsAfterFinished (or kept in history).
flowchart LR
A["CronJob<br/>schedule: 0 2 * * *"] --> B{Firing time?}
B -->|yes| C{Concurrency<br/>policy}
C -->|Allow| D[Create Job]
C -->|Forbid| E{Previous Job<br/>still running?}
C -->|Replace| F["Delete previous Job<br/>create new"]
E -->|yes| G[Skip this run]
E -->|no| D
D --> H[Job runs]
H --> I[Complete]
F --> H
Schedule syntax
The schedule is standard cron:
minute hour day-of-month month day-of-week
"0 2 * * *" # 02:00 every day
"*/15 * * * *" # every 15 minutes
"0 0 * * 0" # midnight on Sunday
"0 9-17 * * 1-5" # 09:00 to 17:00 on weekdays
The schedule is evaluated in the controller’s timezone. The
timeZone field (Kubernetes 1.27+) specifies the zone for
the schedule; default is the controller’s local time, which
can be ambiguous in a distributed cluster.
flowchart LR
A["Schedule: 0 2 * * *"] --> B{timeZone}
B -->|Etc/UTC| C["02:00 UTC"]
B -->|America/New_York| D["02:00 EST/EDT"]
B -->|not set| E[Controller local time]
Concurrency policy
When a CronJob’s previous Job is still running and the schedule fires again, what happens?
| Policy | Behaviour |
|---|---|
Allow | Create the new Job; multiple Jobs run concurrently |
Forbid | Skip the new run; previous Job continues |
Replace | Delete the previous Job; create the new one |
The default is Allow. For most batch workloads, this is
wrong:
- A nightly backup that overlaps with itself — two backups
running at once, contending for the same database. The
Allowdefault produces duplicate backups; the second Job may fail. - A periodic log shipper that overlaps with itself — log files are processed twice.
- A nightly data sync that overlaps — duplicate sync, wasted bandwidth.
The right policy depends on the workload:
- Idempotent and side-effect-free:
Allowis fine (most reporting jobs). - Mutually exclusive:
Forbid(most backups, syncs). - Always want the latest run:
Replace(a health check that needs to run “now,” not at the scheduled time).
flowchart TB
A["02:00 daily<br/>backup Job"] --> B{Runs for 4 hours}
B --> C["Next day 02:00<br/>previous still running"]
C --> D{concurrencyPolicy}
D -->|Allow| E["Create new Job<br/>two running"]
D -->|Forbid| F[Skip this run]
D -->|Replace| G["Kill old Job<br/>create new"]
startingDeadlineSeconds and missed runs
If the controller is down at the firing time (control-plane
restart, network blip), the schedule is missed. The
startingDeadlineSeconds field bounds how late a Job can
start and still count as “on schedule”:
spec:
startingDeadlineSeconds: 300
If the controller is back up within 300 seconds of the firing time, the Job is created. If the delay exceeds 300 seconds, the run is skipped (no Job is created).
Without startingDeadlineSeconds, missed runs accumulate
silently. The operator notices only when a downstream
system (e.g., a daily report) is missing.
History limits
spec:
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
The number of completed (succeeded / failed) Jobs kept for inspection. Old Jobs are deleted when the limit is exceeded.
| Limit | Default | Production |
|---|---|---|
successfulJobsHistoryLimit | 3 | 3 (default is fine) |
failedJobsHistoryLimit | 1 | 3 (more visibility into failures) |
The default for failed jobs is 1; production typically wants 3 so the operator can correlate multiple failures across the history.
Suspending a CronJob
spec:
suspend: true
Setting suspend: true stops new Job creation. Existing
Jobs continue. Setting suspend: false resumes. This is
useful during maintenance windows or upstream outages.
Real production example
A nightly database backup with full retry and history:
apiVersion: batch/v1
kind: CronJob
metadata:
name: db-backup
namespace: data
spec:
schedule: "0 2 * * *"
timeZone: "Etc/UTC"
startingDeadlineSeconds: 600
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 7
failedJobsHistoryLimit: 7
suspend: false
jobTemplate:
spec:
backoffLimit: 3
activeDeadlineSeconds: 7200
ttlSecondsAfterFinished: 604800 # keep 7 days
template:
spec:
restartPolicy: OnFailure
serviceAccountName: backup-runner
containers:
- name: backup
image: backup:v1.4.2
env:
- name: BACKUP_TARGET
value: postgres.prod
- name: BACKUP_BUCKET
value: s3://prod-backups
resources:
requests:
cpu: 1
memory: 2Gi
limits:
cpu: 2
memory: 4Gi
This CronJob:
- Runs at 02:00 UTC every day.
- Skips if the previous backup is still running.
- Retries up to 3 times on failure.
- Bounds to 2 hours of total duration.
- Keeps 7 days of history for inspection.
- Tolerates 10 minutes of controller downtime before considering a run missed.
Inspecting a CronJob
kubectl get cronjob -A
# NAMESPACE NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
# data db-backup 0 2 * * * False 0 2h 30d
# reporting weekly-report 0 9 * * 1 False 0 4d 60d
kubectl describe cronjob db-backup -n data
# Name: db-backup
# Namespace: data
# Schedule: 0 2 * * *
# TimeZone: Etc/UTC
# Concurrency Policy: Forbid
# Suspend: False
# Successful Job History Limit: 7
# Failed Job History Limit: 7
# Starting Deadline Seconds: 600
# ...
The LAST SCHEDULE column shows the last firing time. The
operator can compare it against the schedule interval to
detect missed runs.
Quiz
Knowledge check · 4 questions
Q1. What does concurrencyPolicy Forbid mean for a CronJob?
Q2. A CronJob with startingDeadlineSeconds 300 will always run every scheduled time even if the controller was down.
Q3. Your nightly backup CronJob is set with concurrencyPolicy Allow and startingDeadlineSeconds undefined. Two nights ago, the controller was down for an hour at 02:00. Diagnose what happened.
CronJob db-backup runs at 02:00 daily. The kube-controller-manager was down from 02:00 to 03:00 on day N-1. The schedule fires during the downtime.
Q4. Why is timeZone important for a CronJob, and what is the default?
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Set
timeZoneexplicitly. A CronJob withouttimeZonedrifts in distributed clusters. - Set
concurrencyPolicydeliberately. The defaultAllowis rarely correct;Forbidis the safe choice for most mutually exclusive work. - Set
startingDeadlineSeconds. Without it, missed runs accumulate silently. - Set history limits. Production needs visibility into recent runs.
- Alert on
LAST SCHEDULEage. A missed run is a silent failure unless the dashboard surfaces it. - Document the run’s recovery path. A CronJob that produces a side effect (database backup, S3 upload) needs a documented restore procedure.
CronJobs are the simplest scheduler in the Kubernetes ecosystem and the most most commonly misconfigured. The defaults work for development; production requires deliberate concurrency policy, timeZone, and deadline configuration.