The fix on Kubernetes 1.27+
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-backup
spec:
schedule: "0 2 * * *"
timeZone: "America/New_York" # ← this field
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: my-backup:latest
command: ["./backup.sh"]
restartPolicy: OnFailure
Now 0 2 * * * means 2 AM Eastern — even from a UTC-configured cluster.
How to check your cluster version
kubectl version --short
If the server version is 1.26 or earlier, spec.timeZone is silently ignored — the field gets stripped on submission. You'll need the workaround below.
Workaround for pre-1.27 clusters
Set the timezone inside the container, and translate your schedule to UTC:
spec:
schedule: "0 6 * * *" # 6 AM UTC = 2 AM Eastern (winter) / 1 AM EDT (summer)
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: my-backup:latest
env:
- name: TZ
value: "America/New_York"
command: ["./backup.sh"]
The TZ env var only affects what your application sees as "current time" — it does NOT change when the CronJob fires. You still have to convert the schedule to UTC manually.
DST gotcha
Even with spec.timeZone set, DST transitions can still cause issues:
- Spring forward: jobs scheduled in the lost hour (typically 2-3 AM) are skipped
- Fall back: jobs scheduled in the repeated hour might fire twice (rare)
Schedule outside the 1-3 AM window in DST-observing timezones, or pin to UTC. See our DST guide.
Common mistakes
Don't set TZ thinking it changes the schedule. The TZ env var only affects what your script sees as "now" — the cron schedule itself runs in the cluster's timezone unless you use spec.timeZone.
Don't use abbreviations like "EST" or "PST" — use full IANA names like America/New_York or America/Los_Angeles. The abbreviations are ambiguous and not always supported.
Don't set both spec.timeZone AND a UTC-converted schedule — that double-converts. If you use spec.timeZone, write the schedule in that timezone directly.
Verifying it works
kubectl get cronjob nightly-backup -o yaml | grep -A1 "schedule\|timeZone"
kubectl get cronjob nightly-backup -o jsonpath='{.status.lastScheduleTime}'
The lastScheduleTime should match what you expect in your configured timezone.