Reference

Cron timezone handling.

Cron schedules are interpreted in some timezone — but which one depends on the system, the implementation, and sometimes the user. Misalignment between the timezone you expect and the timezone cron uses is one of the most common production bugs. This guide explains exactly how each piece of the stack chooses a timezone.

The default timezone

By default, cron uses the system's local timezone — the same one shown by date or timedatectl.

On cloud servers, this is almost always UTC. On developer laptops or on-prem servers, it's whatever was set at install time. To check:

date                      # Shows current local time + timezone
timedatectl               # systemd; also shows the TZ name (e.g. "America/New_York")
cat /etc/timezone         # The configured timezone name on Debian/Ubuntu
readlink /etc/localtime   # Symlink target on most modern Linuxes

Setting CRON_TZ in your crontab

Vixie cron 4.0 and newer (the default on most Linux distributions) supports a CRON_TZ variable at the top of the crontab. It changes the timezone for all jobs in that crontab:

CRON_TZ=America/New_York

# Now this means 9 AM Eastern, not 9 AM UTC:
0 9 * * 1-5 /path/to/script.sh

The value must be a tz database name (IANA timezone name). America/New_York works; EST usually does not. UTC works everywhere as an explicit timezone.

Note: CRON_TZ only affects when jobs fire. It does NOT set TZ inside the running job — your script still sees the system timezone unless you set TZ separately.

Per-job timezone with the TZ env var

You can set TZ on a single job by prefixing it:

0 9 * * 1-5 TZ='America/Los_Angeles' /path/to/script.sh

This affects what your script sees inside (e.g., date commands in the script), but it does NOT affect when cron fires the job. To control both the firing time AND the script's view, use both:

CRON_TZ=America/Los_Angeles
0 9 * * 1-5 TZ='America/Los_Angeles' /path/to/script.sh

Changing the system timezone

If you control the server and want everything in a specific timezone, change the system TZ instead of fighting per-job overrides:

sudo timedatectl set-timezone America/New_York
sudo systemctl restart cron     # Reload cron with the new TZ

This affects all users, all jobs, the system clock display, log timestamps — everything. Use with care on shared servers.

Platform-by-platform behavior

Linux/macOS crontab

System timezone unless overridden by CRON_TZ. Vixie cron + cronie on RHEL, ISC cron on Debian/Ubuntu, all support the same basic mechanism.

systemd timers

System timezone by default. Per-timer override with OnCalendar=Mon..Fri 09:00 America/New_York syntax. Recommended over crontab for modern systems because the syntax is explicit.

Kubernetes CronJob

UTC always — until Kubernetes 1.27. From 1.27+, set spec.timeZone: "America/New_York":

apiVersion: batch/v1
kind: CronJob
metadata:
  name: nightly-backup
spec:
  schedule: "0 2 * * *"
  timeZone: "America/New_York"
  jobTemplate: ...

Older clusters: run in UTC and convert mentally, or set TZ in the container spec.

AWS EventBridge

UTC always for classic EventBridge cron expressions. No per-rule timezone setting. AWS introduced "EventBridge Scheduler" (a separate service) in late 2022 that supports timezones via the ScheduleExpressionTimezone field.

GitHub Actions

UTC always. No override. Period. If you need 9 AM Pacific, schedule for 17:00 (or 16:00 during DST) UTC and accept the half-year drift, or use an external scheduler to trigger via workflow_dispatch.

GCP Cloud Scheduler

Per-job time_zone setting. Default UTC, but specifying America/New_York works. This is one of the cleaner timezone implementations.

Quartz

Per-trigger. Use CronScheduleBuilder.cronSchedule("0 0 9 ? * MON-FRI").inTimeZone(TimeZone.getTimeZone("America/New_York")). Defaults to the JVM's timezone (which inherits from the OS).

Spring @Scheduled

The zone attribute:

@Scheduled(cron = "0 0 9 * * MON-FRI", zone = "America/New_York")
public void run() { ... }

Default is the JVM's timezone.

Best practices

  1. For server-side batch jobs, use UTC. Eliminates DST and ambiguity entirely. Convert your "I want it at 9 AM Eastern" requirement to the equivalent UTC hour and document the choice.
  2. For user-facing scheduled tasks (notifications, reports for end users), use a real scheduler with timezone support. Cloud Scheduler, EventBridge Scheduler, or Quartz. Don't try to make UTC-only services do something they can't.
  3. Document the timezone in every cron file with a comment at the top:
    # All times in UTC unless prefixed with TZ
    0 9 * * 1-5 ...
  4. Verify the timezone explicitly in your application's logs. If "9:00:00" appears in logs, it should be obvious whether that's UTC or local.
  5. Don't trust the abbreviations. "EST" might mean Eastern Standard Time on one system and Egypt Standard Time on another. Always use full IANA names like America/New_York.
Related

Continue reading.