Developer guide

Cron Timezone: How to Schedule in UTC, Local or Your Timezone

“It runs at the wrong hour” is the most common cron mystery after “it never runs.” The cause is almost always the timezone: cron evaluates your expression against the server’s wall clock — usually UTC — not against the clock in your head. This guide shows how to schedule in UTC, local or any IANA timezone on crontab, Kubernetes, GitLab and GitHub Actions, with India (IST) as the running example.

Benjamin Rotshtein

Written by Benjamin Rotshtein

Updated

How do I set the timezone for a cron job?

On Linux crontab, add a CRON_TZ=Europe/Berlin line above your schedule, or use TZ=.... Managed platforms have their own field: Kubernetes uses spec.timeZone, GitLab and GitHub Actions use timezone settings in the pipeline YAML. This guide covers each one and how to verify the job actually fires in the zone you expect.

What clock does cron use?
The server’s local wall clock — date. On cloud VMs that is UTC.
Set it in crontab?
CRON_TZ=Asia/Kolkata on cronie, then write the schedule in IST.
GitHub Actions / GitLab?
Schedules are evaluated in UTC — convert the time to UTC and set env: TZ for the job.

Cron uses the server wall clock

There is no timezone stored inside a cron expression. Cron reads the host’s clock — the same one date prints — and fires whenever the local time matches. On a VM or container that clock is UTC unless someone changed it. First step on any machine: know what cron is actually looking at.

date             # -> Thu Aug 14 09:00:00 UTC 2026  (or IST/EDT etc.)
timedatectl      # -> Time zone: Etc/UTC  (systemd hosts)
cat /etc/timezone

If you wrote 0 9 * * * expecting 9 AM your time and the box says UTC, the job fires at 9 AM UTC — which is 2:30 PM in IST. That 5.5-hour shift is the “wrong hour” bug in its purest form.

CRON_TZ vs TZ: the two different settings

The pair of variables trips people up because they do different jobs. CRON_TZ (cronie only) changes when the expression is evaluated — you write the schedule in your timezone and cron converts. TZ changes the timezone thejob’s process sees at runtime (date inside the script) — it does not move the fire time.

# crontab -e  (cronie / Debian-family)
CRON_TZ=Asia/Kolkata
0 8 * * * /opt/scripts/report.sh     # fires at 08:00 IST, converted to server UTC

# inside report.sh: runtime clock for the process
TZ=Asia/Kolkata date                  # -> 08:00:00 IST

Kubernetes CronJob: the timeZone field

Kubernetes has a first-class timezone on the schedule itself. Add timeZone at the top level of the CronJob spec (GA in 1.27) and the controller evaluates the cron expression in that zone:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: report
spec:
  schedule: "0 8 * * *"        # 8 AM in the timeZone below
  timeZone: "Asia/Kolkata"     # k8s 1.27+
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: report
              image: busybox
              command: ["/bin/sh", "-c", "date; /opt/report.sh"]

On older clusters the field is ignored, so either bump Kubernetes or convert the expression to UTC yourself and set env: TZ on the container so the job’s runtime clock matches expectations. Full reference in the Kubernetes CronJob guide.

GitHub Actions and GitLab: schedules are always UTC

Neither platform has a timezone key on the schedule — GitHub Actions cron and GitLab pipeline schedules are both evaluated in UTC. The two-step recipe: convert your local wall time to UTC, then set the TZ environment variable so commands inside the job see the right clock.

# GitHub Actions — 8 AM IST = 02:30 UTC = cron "30 2 * * *"
on:
  schedule:
    - cron: "30 2 * * *"
jobs:
  report:
    runs-on: ubuntu-latest
    env:
      TZ: "Asia/Kolkata"
    steps:
      - run: date   # -> 08:30 IST, because env TZ applies

# GitLab — same idea
# variables: { TZ: "Asia/Kolkata" }
# schedule cron in UTC

India / IST example

IST is UTC+5:30, so any local time in India is 5 hours 30 minutes ahead of the UTC clock cron uses on many platforms. A “9 AM IST daily” job:

crontab (cronie):     CRON_TZ=Asia/Kolkata  +  0 9 * * *  /path/job
Kubernetes 1.27+:     schedule "0 9 * * *"  +  timeZone: "Asia/Kolkata"
GitHub Actions:       cron "30 3 * * *"  (09:00 IST = 03:30 UTC)
GitLab:               cron "30 3 * * *"  +  variables: TZ: "Asia/Kolkata"
node-cron:            cron.schedule("0 9 * * *", fn, { timezone: "Asia/Kolkata" })

Always use IANA identifiers (Asia/Kolkata), never ambiguous abbreviations. The DST guide covers the seasonal traps that UTC avoids entirely.

Verify the schedule fires at the right hour

Don’t guess — prove it. Start with a job that writes its fire time, watch it once, and compare with the clock you expected:

# crontab -e
CRON_TZ=Asia/Kolkata
* * * * * date '+%F %T %Z' >> /tmp/cron-test.log 2>&1
# wait a minute, then:
# cat /tmp/cron-test.log  -> 2026-08-14 09:01:00 IST

The generator’s next fire times preview is the same check without waiting: enter the expression, pick the timezone, and confirm the first fire is the exact minute you intend before it ever lands in production.

Frequently asked questions

What timezone does cron use?

Cron uses the server's local wall clock — the same timezone as `date`. On cloud VMs and containers that is almost always UTC. The schedule expression is evaluated against that clock, so a job written as 0 9 * * * fires at 09:00 server time, which may be 14:30 IST or 04:00 Eastern depending on the box.

How do I set the timezone in crontab?

On cronie (Debian/Ubuntu/RHEL) add a CRON_TZ= line at the top of the crontab followed by the schedule: CRON_TZ=Asia/Kolkata then 0 9 * * * command — cron converts that IST wall time to server time internally. An alternative is setting TZ=... on the line or inside the script, which only changes the timezone the job sees at runtime, not when cron evaluates the expression.

How do I schedule cron in IST (India time)?

Write the schedule in IST using an IANA zone. In crontab: CRON_TZ=Asia/Kolkata then 0 8 * * * to run at 8 AM IST. On GitHub Actions the cron field itself is UTC, so 8 AM IST becomes 0 2 * * * (IST is UTC+5:30) — and you can still set env: TZ: 'Asia/Kolkata' inside the job. In GitLab the same applies: the schedule is UTC, so shift the expression and/or set variables: TZ: 'Asia/Kolkata'.

How do I set the timezone on a Kubernetes CronJob?

Add a top-level timeZone field to the CronJob spec: timeZone: 'Asia/Kolkata' (stable since Kubernetes 1.27). The schedule is then evaluated in that timezone and converted to UTC by the controller. On older clusters, either set env TZ inside the container (affects the job's runtime clock, not the schedule), or convert the expression to UTC yourself.

GitHub Actions and GitLab: is the cron timezone UTC?

Yes. Both evaluate cron schedules in UTC — there is no 'timezone' key in GitHub Actions workflow syntax, and GitLab schedules are stored as UTC too. To fire at 9 AM your local time, convert the time to UTC (e.g. 9 AM IST → 03:30 UTC → cron '30 3 * * *') and optionally set the TZ environment variable so commands inside the job see your local time.

How do I verify my cron timezone is correct?

Three checks. 1) Read the server clock: `date` and `timedatectl` show the wall-clock timezone cron evaluates against. 2) Test the expression with a quick job that logs timestamps: * * * * * date >> /tmp/cron-test.log and compare the times with your expectation. 3) Use the generator's next-fire-times preview — feed the expression plus timezone and confirm the first run is the minute you intend.

Related guides

Build a schedule that survives

Generate the expression in plain English and preview the next five fire times in your timezone — before a “wrong hour” bug reaches production.