Crontab Timezone Handling: CRON_TZ, TZ, and DST
The clock on the server decides what your cron expression means. This single fact is the source of most timezone confusion in production systems: a server configured in UTC and a developer in Europe/Paris will read the same schedule expression very differently. Understanding where the timezone is set, and how each platform handles daylight saving time, prevents jobs from running at the wrong hour.1
Most systems provide at least one escape hatch: the CRON_TZ variable (Linux vixie-cron and derivatives), the TZ variable (older BSDs), or a per-schedule timezone picker (GitLab CI, Render). Platforms that lock to UTC (GitHub Actions, AWS EventBridge, Cloudflare Workers) require you to convert your target time to UTC manually before writing the expression.
Setting timezone in Linux crontab
Add CRON_TZ=America/New_York (or any IANA timezone name) to the crontab above the jobs that should use it. Each subsequent job line runs as if the system clock is in that timezone. The variable applies to all lines below it until another CRON_TZ= appears. The underlying system clock does not change; only the interpretation of the expression changes. Some older cron implementations use TZ= instead of CRON_TZ=; check man 5 crontab on your system.1 Understanding this distinction prevents the frustrating situation where a job fires at the correct UTC offset on one server but an hour off on another because the variable name differs.
Keeping timezone assignments near the jobs they affect
Place CRON_TZ directly above the group of jobs that share the same local time. If the crontab grows, this makes the intended timezone visible next to the expression instead of buried at the top of the file. A later maintainer can see the rule and the timezone together, which reduces accidental edits. When multiple timezones appear in one crontab, add a comment above each CRON_TZ assignment naming the target region so the intent is visible without reverse-engineering the IANA timezone name.
Remember that CRON_TZ cascades downward and changes only the interpretation of later lines, so a single assignment near the top silently reshapes every job beneath it. The system clock keeps running in its original zone, which is why the same crontab can look correct yet behave differently after a server move. When you audit a file with several timezones, read each block from its nearest assignment upward rather than assuming one global setting applies throughout.
DST transitions and double-scheduling
A daily job scheduled for 0 2 * * * may fire twice or not at all during a DST spring-forward or fall-back. When clocks move forward by one hour (e.g., 2:00 AM becomes 3:00 AM), 2 AM does not exist, and the daemon skips the job for that interval. When clocks move back (2:00 AM becomes 1:00 AM again), the daemon fires the job twice.2 Consequently, scheduling at 3 AM or later avoids most DST problems because the transition is complete before the job runs.
The safest strategy for jobs that must fire exactly once per day is to schedule them outside the 1 AM to 3 AM window entirely, or to run them in UTC where DST transitions do not apply. For jobs that cannot avoid the risky window, add a script-level guard that checks whether the job already ran in the current UTC day and exits early if it did, preventing duplicate work from the fall-back transition.
Per-platform timezone support
GitLab CI and Render let you select an IANA timezone per schedule in the dashboard, converting the expression to UTC internally. Kubernetes 1.27+ supports the spec.timeZone field for named IANA timezone scheduling.3 systemd timers accept a timezone directly in the OnCalendar expression, making them DST-aware without any extra configuration.
Handling UTC-only deployments
GitHub Actions, standard AWS EventBridge cron expressions, and Cloudflare Workers Cron Triggers run in UTC only; you cannot override the timezone in these environments. When targeting a UTC-only platform, convert your local time to UTC before writing the expression, and add a comment documenting the intended local time so future maintainers know the target. AWS EventBridge Scheduler (a separate service from EventBridge cron expressions) supports a TimeZone parameter,4 but the classic cron() format does not. For jobs that must follow local business hours on a UTC-only platform, consider running the schedule evaluation inside the job itself rather than relying on the platform's cron parser.
Choosing a safe scheduling time on DST-affected servers
For recurring jobs on servers that observe daylight saving time, the 2 AM hour is the highest-risk window: it either does not exist (spring forward) or occurs twice (fall back). Jobs scheduled between 1 AM and 3 AM are vulnerable on any IANA timezone that observes DST. Scheduling at 4 AM or later eliminates this risk entirely, because the transition is complete before 4 AM in every IANA timezone.
Auditing existing crontabs for DST-vulnerable entries
To audit an existing crontab for DST-vulnerable entries, list all jobs with crontab -l and check any expression where the hour field contains 1, 2, or 3. For servers in DST-observing timezones, move those jobs to hour 4 or later. For jobs that must run at a specific time within the risky window, switch the cron timezone to UTC and convert the target time to UTC manually before writing the expression.
Migrating crontabs to UTC-only platforms
Migrating cron jobs from a server with CRON_TZ support to a UTC-only platform (GitHub Actions, AWS EventBridge, Cloudflare Workers) requires converting each expression manually. For each job, determine the UTC equivalent of the target local time and account for DST if the local timezone observes it. Skipping the DST adjustment is how a job that ran reliably on a Linux server ends up firing an hour early after a move to GitHub Actions.
The safest approach is to pick one UTC time that works year-round: convert your local time to a UTC cron schedule before you commit it to a UTC-only platform, and document the corresponding local time in a comment above the cron entry. A job firing at 9 AM US Eastern Standard Time (UTC-5) uses 0 14 * * *. During Eastern Daylight Time (UTC-4), 14:00 UTC is 10 AM local rather than 9 AM; accept this one-hour seasonal drift and document it, rather than maintaining two different expressions per job.
When to use this
Use this guide when your cron jobs fire at the wrong local time, when deploying servers in multiple regions that must run jobs in local business hours, when investigating a DST-related double-firing or missed-run incident, or when migrating crontabs to a UTC-only platform.
Examples
Schedule a job at 9 AM Eastern Time on a UTC server
0 9 * * 1-5
CRON_TZ=America/New_York 0 9 * * 1-5
CRON_TZ=America/New_York sets the timezone for the lines that follow. The job fires at 9 AM ET, which is 14:00 UTC in winter and 13:00 UTC in summer.
Avoid DST double-firing for a daily job
0 2 * * *
0 4 * * *
Scheduling at 4 AM instead of 2 AM places the job safely outside the DST transition window in most IANA timezones.
- 1.
Linux man7, "crontab(5) — Linux manual page," man7.org, accessed June 2026. https://www.man7.org/linux/man-pages/man5/crontab.5.html
- 2.
Cronpreview, "The cron timezone and DST survival guide," cronpreview.com, accessed June 2026. https://cronpreview.com/guides/timezone-and-dst-survival-guide
- 3.
Kubernetes, "CronJob - Time Zones," kubernetes.io, accessed June 2026. https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/#time-zones
- 4.
Amazon Web Services, "Schedule types in EventBridge Scheduler," docs.aws.amazon.com, accessed June 2026. https://docs.aws.amazon.com/scheduler/latest/UserGuide/schedule-types.html