Cron and timezones

Cron runs in local time. Twice a year that means a scheduled hour either never happens or happens twice — with no error either way.

The hour that does not exist

America/New_York, 8 March 2026

01:59 EST  ─┐
            ├─ 02:00 to 02:59 never occurs
03:00 EDT  ─┘

30 2 * * *   ← scheduled here. No moment to fire on.

Clocks jump straight from 02:00 to 03:00, so an entire hour of local time is skipped. A job scheduled inside it has no instant to run at. Some implementations, Vixie cron among them, apply heuristics to run once-daily jobs from the shifted window anyway — but the behaviour is not uniform across crons, and nothing tells you which one you have.

The hour that happens twice

America/New_York, 1 November 2026

01:30 EDT  →  05:30 UTC   ← first pass
01:30 EST  →  06:30 UTC   ← second pass, one hour later

Same wall-clock reading. Two distinct instants.

We confirmed the two readings resolve to instants exactly an hour apart. A job scheduled at 01:30 can therefore fire twice — which for a report is a duplicate, and for anything that moves money or sends mail is a real incident.

Neither case writes a log line saying what happened. A job that silently skipped a night looks identical to one that was never scheduled, and a job that ran twice looks like a bug in the job.

Setting a timezone explicitly

CRON_TZ=America/New_York
0 9 * * MON-FRI   /usr/local/bin/morning-report.sh

CRON_TZ=UTC
*/15 * * * *      /usr/local/bin/health-check.sh

CRON_TZ applies to every entry below it until another one appears, so a single crontab can mix zones. Use a full IANA name — America/New_York, Asia/Seoul — rather than an abbreviation. CST means both Central Standard Time and China Standard Time, and nothing will tell you which one it picked.

Not every implementation supports CRON_TZ. If yours does not, a systemd timer expresses the same thing through OnCalendar with an explicit zone.

Or avoid the problem entirely

UTC has no daylight saving. No hour ever repeats or disappears, every schedule means exactly what it says, and logs from different machines line up without conversion — which matters more than it sounds when you are correlating events during an incident.

The cost is real though: a job meant to align with human activity now needs the offset applied by hand, and that offset moves twice a year in most of the world. Zones without daylight saving — Korea, Japan, India, most of Asia — avoid this entirely, which is worth knowing before assuming you have the problem at all.

The practical rule

Job typeTimezoneNote
Health checks, backups, cleanupUTCno human alignment needed
Reports, notificationsCRON_TZ = business zoneshould track the wall clock
Anything at allavoid 01:00–03:00 local

That last row is the cheapest defence available. Schedules outside the transition window are unaffected by daylight saving regardless of which zone they run in, and moving a nightly job from 02:30 to 04:30 costs nothing. See cron every 5 minutes for the interval traps, and crontab syntax for the field reference.

Frequently asked questions

What timezone does cron use?

The system's local timezone, not UTC, unless the crontab sets CRON_TZ or the platform documents otherwise. That is a reasonable default for a machine sitting in one office and a liability for a server whose location is an accident of hosting. The practical consequence is that the same crontab produces different real-world times on two machines, and neither reports anything unusual. Check what the server actually thinks with timedatectl or date, rather than assuming, because a container inheriting UTC while its host runs local time is a common and quiet source of disagreement, and one that only surfaces when someone compares two logs during an incident. Verify it explicitly rather than inferring it from where the server is billed.

What happens to a cron job during daylight saving?

It either does not run or runs twice, depending on the direction. When clocks spring forward, an hour of local time simply does not occur — in New York on 8 March 2026 the clock jumps from 02:00 to 03:00, so a job scheduled at 02:30 has no moment to fire. When clocks fall back, 01:30 happens twice with an hour between them, and we confirmed the two instants differ by exactly that: 05:30 and 06:30 UTC for the same wall-clock reading. Vixie cron applies heuristics for jobs within the shifted window, but the behaviour differs between implementations, so the safe assumption is that a job in the shifted window may not run at all. Schedule outside the transition window and the question stops mattering.

How do I set a timezone for a cron job?

Put CRON_TZ=Region/City on its own line in the crontab, above the entries it should apply to. Everything after it is interpreted in that zone until another CRON_TZ appears, so you can mix zones in one file. Use a full IANA name such as America/New_York rather than an abbreviation: EST and CST are ambiguous, since CST means both Central Standard Time and China Standard Time. Not every cron implementation supports CRON_TZ, so check yours; systemd timers express the same thing with OnCalendar and an explicit timezone suffix, which is worth preferring where systemd is already present, since it states the zone in the unit rather than depending on crontab support. Put the CRON_TZ line above every entry it governs, since it applies downward only.

Should I just run servers on UTC?

For anything infrastructural, yes, and it removes this entire class of problem rather than mitigating it. UTC has no daylight saving, so no hour ever repeats or disappears and every schedule means exactly what it says. Logs from different machines line up without conversion, which matters more than it sounds during an incident. The cost is that a job meant to align with human activity — a report before the workday, a batch during a quiet window — now needs the offset applied by hand, and that offset changes twice a year in most of the world, so a hand-applied offset needs revisiting rather than setting once and forgetting. Most infrastructure jobs need no human alignment at all, so this costs nothing for them.

How do I schedule reliably for business hours in a DST zone?

Set CRON_TZ to the business zone so the schedule tracks the wall clock people actually use, and keep the times away from the transition window — nothing between 01:00 and 03:00 local. That combination gives you 09:00 meaning nine o'clock all year without ever landing in the hour that vanishes or repeats. If the job genuinely must run in the small hours, run it on UTC at a fixed offset and accept that it drifts an hour relative to local time twice a year, which is usually the lesser problem than not running at all, or running twice and sending every notification a second time to the same recipients. Nothing between 01:00 and 03:00 local is the single cheapest rule here.

References

Check an expression's run times with the cron parser — it computes in UTC deliberately, so you apply your own offset. More tools at withuse.io/tools.