How to Set Up CronJobs in Kubernetes (Complete Guide)
Learn how to create and manage Kubernetes CronJobs. Covers YAML manifests, concurrency policies, timezone support, debugging, and Helm chart examples.
Prerequisites
- Kubernetes 1.27+
- kubectl configured
- Basic understanding of Pods and Jobs
In this guide
What Is a Kubernetes CronJob?
A Kubernetes CronJob creates Jobs on a repeating schedule. Each Job spawns one or more Pods to perform work. When the work is done, the Pods terminate. Kubernetes handles scheduling, retries, history cleanup, and concurrency.
CronJobs use standard 5-field cron syntax and support timezone configuration (since Kubernetes 1.27).
Quick Start
Create a CronJob with kubectl:
kubectl create cronjob hello --image=busybox --schedule="*/5 * * * *" -- echo "Hello from Kubernetes"Or apply a YAML manifest:
apiVersion: batch/v1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/5 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
command: ["echo", "Hello from Kubernetes"]
restartPolicy: OnFailurekubectl apply -f cronjob.yaml
kubectl get cronjobsCronJob Manifest Deep Dive
A production-ready CronJob manifest:
apiVersion: batch/v1
kind: CronJob
metadata:
name: db-backup
namespace: production
spec:
schedule: "0 2 * * *"
timeZone: "America/New_York"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5
startingDeadlineSeconds: 600
jobTemplate:
spec:
backoffLimit: 3
activeDeadlineSeconds: 3600
template:
spec:
containers:
- name: backup
image: myapp/db-backup:latest
env:
- name: DB_HOST
valueFrom:
secretKeyRef:
name: db-credentials
key: host
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
restartPolicy: OnFailure
serviceAccountName: backup-saKey fields:
- startingDeadlineSeconds — If a scheduled run is missed by more than this many seconds, skip it
- backoffLimit — Number of retries before marking the Job as failed
- activeDeadlineSeconds — Maximum runtime for the Job
Concurrency Policies
Control what happens when a new CronJob is due while the previous one is still running:
- Allow (default) — Multiple Jobs can run concurrently
- Forbid — Skip the new Job if the previous one is still running
- Replace — Cancel the running Job and start a new one
spec:
concurrencyPolicy: ForbidFor most production workloads, use Forbid to prevent resource contention and duplicate processing.
Timezone Support
Since Kubernetes 1.27, CronJobs support the timeZone field:
spec:
schedule: "0 9 * * 1-5"
timeZone: "America/New_York"Without timeZone, the schedule is interpreted in the kube-controller-manager's timezone (usually UTC).
Uses IANA timezone names. Check supported timezones:
kubectl exec -it <pod> -- cat /usr/share/zoneinfo/tzdata.zi | headCommon Cron Expressions
Standard 5-field format:
*/5 * * * *— Every 5 minutes0 * * * *— Every hour0 0 * * *— Daily at midnight0 2 * * *— Daily at 2 AM (common for backups)0 9 * * 1-5— Weekdays at 9 AM0 0 1 * *— First day of every month0 0 * * 0— Every Sunday at midnight
Every weekday at 9:00 AM
Next runs (UTC):
Mon, May 18, 2026 09:00
Tue, May 19, 2026 09:00
Wed, May 20, 2026 09:00
Debugging CronJobs
Check CronJob status:
kubectl get cronjobs
kubectl describe cronjob db-backupView Job history:
kubectl get jobs --sort-by=.metadata.creationTimestampCheck Pod logs:
kubectl get pods --selector=job-name=db-backup-28394520
kubectl logs db-backup-28394520-xyzCommon issues:
- Jobs not starting: Check
startingDeadlineSecondsandconcurrencyPolicy - Jobs failing: Check Pod logs and
backoffLimit - Too many Pods: Reduce
successfulJobsHistoryLimitandfailedJobsHistoryLimit
Suspend a CronJob:
kubectl patch cronjob db-backup -p '{"spec":{"suspend":true}}'Helm Chart Example
A reusable Helm template for CronJobs:
# templates/cronjob.yaml
{{- range .Values.cronjobs }}
apiVersion: batch/v1
kind: CronJob
metadata:
name: {{ .name }}
spec:
schedule: {{ .schedule | quote }}
concurrencyPolicy: {{ .concurrencyPolicy | default "Forbid" }}
jobTemplate:
spec:
template:
spec:
containers:
- name: {{ .name }}
image: "{{ $.Values.image.repository }}:{{ $.Values.image.tag }}"
command: {{ .command | toJson }}
restartPolicy: OnFailure
---
{{- end }}# values.yaml
image:
repository: myapp
tag: latest
cronjobs:
- name: db-backup
schedule: "0 2 * * *"
command: ["python", "backup.py"]
- name: cleanup
schedule: "0 */6 * * *"
command: ["python", "cleanup.py"]Production Tips
Resource requests/limits: Always set them to prevent CronJobs from starving other workloads.
History limits: Keep them low to avoid accumulating old Pods:
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5Monitoring: Use Prometheus + kube-state-metrics to alert on:
kube_cronjob_next_schedule_time— detect missed scheduleskube_job_failed— detect failures
Deadline: Set startingDeadlineSeconds to avoid running stale jobs after cluster maintenance.