Cron on Kubernetes vs Linux Crontab: Timezone Traps

Why the same five-field cron string fires at different times on GitHub Actions, K8s CronJob, and server crontab—and how to preview safely.

0 9 * * 1-5 looks unambiguous: minute zero, hour nine, weekdays. Until you deploy it on three platforms and backups run at 09:00 local, 09:00 UTC, and 09:00 in the control plane’s zone—all from the same string.

This comparison is for engineers who copy cron lines between Linux servers, Kubernetes, and CI without re-reading timezone rules.

Same grammar, different clocks

Classic Unix cron uses five fields (minute, hour, day-of-month, month, day-of-week). The grammar is familiar; the clock is not shared:

PlatformTypical timezone behavior
Linux user crontabServer local timezone (timedatectl)
Kubernetes CronJobController default unless timeZone set (1.25+)
GitHub Actions scheduleAlways UTC
Quartz / some cloud rulesOften six fields (seconds first)

A line copied from /etc/crontab on a Shanghai VM to a GitHub workflow will shift by eight hours unless you convert.

Linux crontab: know the server

crontab -e on a VM evaluates expressions in the machine’s local zone. DST changes can skip or repeat an hour for wall-clock schedules like 30 2 * * *.

Runbooks should record:

  • Expression
  • Server timedatectl output
  • Expected UTC equivalent for log correlation

When correlating with CloudWatch or Loki timestamps, convert with a Timestamp Converter instead of mental math during an incident.

Kubernetes: set timeZone explicitly

Since Kubernetes 1.25, CronJob supports:

spec:
  timeZone: "Asia/Shanghai"
  schedule: "0 9 * * 1-5"

Without timeZone, behavior depends on controller configuration—do not assume it matches your laptop. Treat missing timezone in YAML as a review blocker.

Cluster upgrades and multi-region control planes are why explicit IANA zones beat "we always use local."

GitHub Actions: UTC only

on:
  schedule:
    - cron: "0 9 * * 1-5"

This is 09:00 UTC Monday–Friday. For Shanghai weekday 09:00, you need 0 1 * * 1-5 in UTC during standard offset—or document the offset table in the workflow README.

Many "why did CI run at night?" tickets start here.

Preview before merge

Regardless of platform:

  1. Parse the expression in a Cron Expression Parser
  2. Read the human description (watch day-of-month OR day-of-week semantics)
  3. Preview next runs in UTC and the target zone
  4. Add timezone + platform to the runbook line

Common copy-paste failures

MistakeResult
Actions cron meant as local 9amJob at wrong wall time
K8s without timeZoneDrift after cluster move
Six-field Quartz pasted as five-fieldInvalid or wrong schedule
DOM + DOW both set expecting ANDExtra unexpected runs

Related learning

Field syntax, platform tables, and a full debugging checklist live in the Cron Expressions course. For log correlation across zones, pair with Timestamps in logs across timezones.

Bottom line

Cron strings are timezone-agnostic; schedulers are not. Document platform + zone next to every line, preview in UTC and local, and set Kubernetes timeZone on purpose—not by accident.

Related tools

Use the tools from this article

Cron Expression Parsercron / crontab / scheduleTimestamp Convertertimestamp / unix / epoch

Learn the format

Cron Expressions CourseLearn Unix cron syntax: five fields, ranges and steps, timezone traps, platform differences, common job patterns, and a practical debugging workflow.Unix Time & Timestamps CourseLearn Unix epoch time, seconds vs milliseconds, UTC, timezones, ISO 8601, and how to debug timestamps in logs and APIs.

Back to articles