15+ real CronJob schedule examples with full YAML specs. Covers timeZone, concurrencyPolicy, startingDeadlineSeconds, suspend, and production troubleshooting.
A Kubernetes CronJob creates Jobs on a repeating schedule using standard 5-field cron syntax. Unlike AWS EventBridge, Kubernetes uses the traditional Linux cron format — 5 fields, 0-indexed weekdays (Sunday=0).
apiVersion: batch/v1
kind: CronJob
metadata:
name: my-scheduled-job
namespace: default
spec:
schedule: "0 9 * * 1-5" # 9 AM UTC, weekdays
timeZone: "America/New_York" # Kubernetes 1.27+ only
concurrencyPolicy: Forbid
startingDeadlineSeconds: 200
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
suspend: false
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: job
image: my-image:latest
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
Before Kubernetes 1.27, all CronJob schedules ran in the timezone of the kube-controller-manager — typically UTC. This required manually calculating UTC offsets for every schedule.
From Kubernetes 1.27, you can specify a timezone directly:
spec: schedule: "0 9 * * 1-5" timeZone: "America/New_York" # Runs at 9 AM Eastern, not 9 AM UTC
timeZone requires Kubernetes 1.27+ with the CronJobTimeZone feature gate enabled (enabled by default in 1.27). On older clusters, the field is silently ignored.
Valid values are IANA timezone names from the IANA timezone database — the same format used by Linux systems: America/New_York, Europe/London, Asia/Kolkata, UTC.
What happens when the next scheduled run fires but the previous one is still running? concurrencyPolicy controls this:
| Policy | Behaviour | Best for |
|---|---|---|
Allow (default) | New job starts alongside existing one | Independent idempotent jobs |
Forbid | Skips the new run if previous is still active | Jobs that must not overlap (DB migrations, file processing) |
Replace | Cancels the running job and starts a new one | Jobs where only the latest run matters |
spec: schedule: "*/10 * * * *" concurrencyPolicy: Forbid # Safe for jobs longer than 10 minutes
If the kube-controller-manager is down and misses scheduled runs, startingDeadlineSeconds controls how late a job can start. If the deadline is exceeded, the run is skipped entirely.
spec: schedule: "0 3 * * *" startingDeadlineSeconds: 3600 # Start up to 1 hour late
startingDeadlineSeconds window (or the last 10 minutes if unset), the CronJob will not fire and logs "too many missed start times". Set startingDeadlineSeconds explicitly to a value smaller than your interval.
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-report
namespace: production
labels:
app: reports
team: data-engineering
spec:
schedule: "0 2 * * *"
timeZone: "UTC"
concurrencyPolicy: Forbid
startingDeadlineSeconds: 600
successfulJobsHistoryLimit: 7
failedJobsHistoryLimit: 3
suspend: false
jobTemplate:
spec:
backoffLimit: 2
activeDeadlineSeconds: 3600 # Kill job after 1 hour
template:
metadata:
labels:
app: reports
spec:
serviceAccountName: report-runner
restartPolicy: OnFailure
containers:
- name: report-generator
image: myregistry.io/reports:v2.4.1
imagePullPolicy: IfNotPresent
command: ["python", "-m", "reports.generate", "--type=nightly"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secrets
key: url
- name: REPORT_DATE
value: "yesterday"
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
kubectl describe cronjob my-cronjob # Check events and status kubectl get jobs --selector=job-name=... # See created jobs kubectl get events --sort-by='.lastTimestamp' | grep CronJob
kubectl create job manual-test --from=cronjob/my-cronjob
kubectl patch cronjob my-cronjob -p '{"spec":{"suspend":true}}' # Suspend
kubectl patch cronjob my-cronjob -p '{"spec":{"suspend":false}}' # Resume
kubectl get cronjob my-cronjob -o jsonpath='{.spec.suspend}'startingDeadlineSeconds or restart the controller.timeZone: "America/New_York" in the spec to use any IANA timezone.Allow (default): concurrent runs are permitted. Forbid: skip new run if previous is still running. Replace: cancel the running job and start fresh. Use Forbid for most production jobs to prevent overlap.suspend: true? 2) Did the controller miss more than 100 runs? 3) Is the image pulling correctly? 4) Are RBAC permissions correct? 5) Is the schedule valid? Use kubectl describe cronjob NAME and kubectl get events.kubectl create job test --from=cronjob/my-cronjob. This creates an immediate Job from the CronJob's template without modifying the schedule.successfulJobsHistoryLimit and failedJobsHistoryLimit. Set them based on how many runs you want available for debugging.