The cron timezone and DST survival guide
Why your 02:30 nightly job ran zero times in March and twice in October, and what to do about it.
Daylight-saving transitions break cron in two specific, predictable ways. Once you have seen each of them happen in production you will never forget the rule. This guide saves you the production incident.
Spring forward: the missing run
On the spring-forward day, the clock skips an hour. In most of the EU this is the last Sunday of March, jumping from 02:00 to 03:00 local. A schedule at 30 2 * * * targets 02:30 — which does not exist that day. What happens next is up to the cron implementation.
- Linux
vixie cron: runs at 03:00 on the gap day (catches up). - Linux
cronie(RHEL family): silently skips. - Go
robfig/cron(used by Kubernetes): silently skips. - Quartz: runs at 03:00 (skip-forward).
- AWS EventBridge Scheduler with a TZ set: skips. UTC mode: not affected.
Fall back: the double run
On the fall-back day, the same wall-clock time happens twice. A schedule at 30 2 * * * hits 02:30 once at the original offset and again at the new offset — same wall-clock time, different UTC instants.
- Linux
vixie cron: fires twice. - Most modern schedulers: fire once (at the first 02:30).
The fix is simple: pick UTC, or pick a safe local time
The bullet-proof options, ranked from best to worst:
- Schedule in UTC. Your downstream timestamps are already UTC anyway, and there are no DST transitions in UTC.
- If you must use a local timezone, avoid the gap window. Don't schedule anything between 01:00 and 03:00 in a DST-observing zone. Pick 04:30 or 05:30 and you are immune.
- Use a timezone that does not observe DST. Examples:
Etc/UTC,Asia/Tokyo,America/Phoenix.
Bonus failure: container default timezone
Most Linux container images ship with the system timezone set to UTC. Many production cron definitions assume Europe-something-Central. The moment a developer tests locally with TZ=Europe/Berlin docker run … and ops deploys without setting TZ, the schedule silently shifts by one or two hours. Set TZ explicitly in the deployment manifest, every time.