Developer guide

Cron & Time Zones: How DST Really Messes Up Schedules

You debug your crontab, it looks right, and yet the job skipped a day — or ran twice. Nine times out of ten the culprit is not your expression but the clock: cron runs in the server's time zone, and that clock sometimes jumps.

Benjamin Rotshtein

Written by Benjamin Rotshtein

Updated

What time does cron actually use?
The time of the server (or container) where cron runs — not your laptop's and not your cron-builder page's.
Does DST break cron?
Yes. Spring-forward can skip a scheduled minute entirely; fall-back can fire it twice. Every zone that observes DST shows this behavior on its two transition days.
Best fix for the discipline?
Keep the server on UTC, or pin the crontab to a fixed zone with CRON_TZ, and schedule jobs where the trigger minute exists at least once.

Cron inherits the server's clock

A cron expression only says when, not in which clock. That clock is the system time of the machine running the daemon. If that machine is UTC (the default on virtually every Linux image), then 0 9 * * * fires at 09:00 UTC. If your users are in Jerusalem, their “9 AM” is 06:00 UTC — and many people discover two weeks in that their “morning” job ran at another zone's plausible hour.

Spring-forward: the hour that never existed

When most of the European Union moves off a zone offset, the clock jumps from 02:00 to 03:00. The whole minute 02:00–02:59 never exists. A cron entry pinned to 0 2 * * * simply does not fire once — you lose that run and do not usually see a notification.

Fall-back: the hour that happens twice

In autumn the clock falls from 03:00 back to 02:00, so the 02:00 hour happens twice. A job set for0 2 * * * can run twice in the same day: once in the first occurrence of 02:00 and once in the repeated one. Idempotency matters — a report that appends instead of overwriting duplicates rows on that day.

Three DST-proof strategies

1. Stay on UTC

UTC has no DST by definition. Pick a UTC minute you can translate to your business hours. Zero skipped or duplicated runs forever — deceptively simple and the single most reliable option.

2. Pin the zone with CRON_TZ

In a crontab file, a governing CRON_TZ=Europe/Jerusalem line makes every following entry evaluate in that zone, including its own DST rules. Not supported by every daemon.

3. Schedule with the generator

Build and validate your expression here, copy it, and pin the server zone to match. Preview each date explicitly to catch spring-forward gaps before deploy.

Generate a valid expression

Fiddling with five fields by hand is where both expression bugs and zone mistakes sneak in. The Cron Generator builds a valid expression from plain-English inputs and shows you the next-fires, so you can confirm the real wall-clock before you schedule.

Frequently asked questions

What timezone does cron run in?

Cron runs on the local time of the server, not UTC and not your personal time zone. A schedule like 0 9 * * * fires at 9:00 AM according to the server's configured time zone. If the server is UTC, it fires at 09:00 UTC.

Does DST break cron jobs?

It can. In spring-forward, an hour is skipped: a schedule set to 02:30 never fires. In fall-back, an hour repeats: a schedule can fire twice. Whether a given job is affected depends on the exact minute and whether the process itself is time-zone aware.

How do I run a cron job in a specific time zone?

The cleanest way is to set the server or container time zone to the one you care about. Some cron implementations also support a CRON_TZ=Zone/Name line in the crontab, which applies to every following entry until the next CRON_TZ.

Should my cron time zone be UTC?

UTC is the safest baseline because it has no daylight saving transitions, so a run at 03:00 UTC always exists and always runs once. The trade-off is that you must manually map UTC to your wall-clock times.

Why did my cron job not run yesterday?

Most likely because spring-forward skipped the target wall-clock minute, or the server clock jumped. Check the crontab time zone against the system TZ="ZoneName" and confirm whether the affected window fell in the skipped hour.