Debugging

Kubernetes CronJob timezone.

Kubernetes CronJob ran in UTC always — until 1.27 (April 2023), when spec.timeZone was added. If you're on 1.27+, set the timezone explicitly. On older clusters, set the TZ environment variable in the container.

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.

Related

Continue reading.