advanced 15 min read

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

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:

bash
kubectl create cronjob hello --image=busybox --schedule="*/5 * * * *" -- echo "Hello from Kubernetes"

Or apply a YAML manifest:

yaml
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: OnFailure
bash
kubectl apply -f cronjob.yaml
kubectl get cronjobs

CronJob Manifest Deep Dive

A production-ready CronJob manifest:

yaml
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-sa

Key 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
yaml
spec:
  concurrencyPolicy: Forbid

For most production workloads, use Forbid to prevent resource contention and duplicate processing.

Timezone Support

Since Kubernetes 1.27, CronJobs support the timeZone field:

yaml
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:

bash
kubectl exec -it <pod> -- cat /usr/share/zoneinfo/tzdata.zi | head

Common Cron Expressions

Standard 5-field format:

  • */5 * * * * — Every 5 minutes
  • 0 * * * * — Every hour
  • 0 0 * * * — Daily at midnight
  • 0 2 * * * — Daily at 2 AM (common for backups)
  • 0 9 * * 1-5 — Weekdays at 9 AM
  • 0 0 1 * * — First day of every month
  • 0 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:

bash
kubectl get cronjobs
kubectl describe cronjob db-backup

View Job history:

bash
kubectl get jobs --sort-by=.metadata.creationTimestamp

Check Pod logs:

bash
kubectl get pods --selector=job-name=db-backup-28394520
kubectl logs db-backup-28394520-xyz

Common issues:

  • Jobs not starting: Check startingDeadlineSeconds and concurrencyPolicy
  • Jobs failing: Check Pod logs and backoffLimit
  • Too many Pods: Reduce successfulJobsHistoryLimit and failedJobsHistoryLimit

Suspend a CronJob:

bash
kubectl patch cronjob db-backup -p '{"spec":{"suspend":true}}'

Helm Chart Example

A reusable Helm template for CronJobs:

yaml
# 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 }}
yaml
# 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:

yaml
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5

Monitoring: Use Prometheus + kube-state-metrics to alert on:

  • kube_cronjob_next_schedule_time — detect missed schedules
  • kube_job_failed — detect failures

Deadline: Set startingDeadlineSeconds to avoid running stale jobs after cluster maintenance.

Frequently Asked Questions