Try the Cron Explainer

Cron and Daylight Saving Time: Why Your 2:30 AM Job Didn't Run β€” or Ran Twice

A cron job scheduled for 2:30 AM doesn't run on "spring forward" night, because 2:30 AM doesn't exist that night β€” and the same job might run twice on "fall back" night, because 2:30 AM happens twice. Here's why this is exactly the time window many maintenance jobs use, why UTC eliminates the problem entirely for most automated jobs, and why "9 AM local time" scheduling that survives DST requires timezone-aware libraries, not static cron expressions.

By sadiqbd Β· June 14, 2026

Share:
Cron and Daylight Saving Time: Why Your 2:30 AM Job Didn't Run β€” or Ran Twice

A cron job scheduled for 2:30 AM doesn't run on the night clocks "spring forward" β€” because 2:30 AM doesn't exist that night β€” and the same job might run twice on the night clocks "fall back," because 2:30 AM happens twice

The previous articles on this site covered cron syntax, the cron daemon's internals, and modern scheduling alternatives. This article addresses a specific, recurring source of "why did this job not run last night" (or "why did it run twice") confusion: cron jobs scheduled in local time, on systems that observe Daylight Saving Time (DST) transitions.


The "spring forward" problem: a time that doesn't exist

When clocks "spring forward" (e.g., 2:00 AM becomes 3:00 AM, in one "jump" β€” the hour from 2:00-2:59 AM doesn't occur that night) β€” a cron job scheduled for, say, 30 2 * * * (2:30 AM daily) has no 2:30 AM to run during on this specific night β€” the clock goes directly from 1:59:59 to 3:00:00, skipping the 2:XX hour entirely.

What happens to the job depends on the cron implementation:

  • Some implementations skip the job entirely for that night β€” the job simply doesn't run, and resumes its normal schedule the following night (when 2:30 AM does exist again)
  • Other implementations run the job immediately when the clock "catches up" β€” e.g., running the job at 3:00 AM (or shortly after), treating the "missed" 2:30 AM trigger as something to "catch up on" as soon as possible after the jump

Neither behavior is "wrong" per se β€” but the specific behavior depends on the cron implementation/configuration β€” and a job expecting to run exactly once per day, at exactly 2:30 AM, might, on the "spring forward" night, either not run at all (if the implementation skips) or run at an unexpected time (if the implementation "catches up") β€” either of which could be surprising/problematic depending on what the job does and what assumptions downstream processes make about "this runs at 2:30 AM, exactly once per day."


The "fall back" problem: a time that occurs twice

When clocks "fall back" (e.g., 2:00 AM, upon reaching 3:00 AM, reverts to 2:00 AM β€” the hour from 2:00-2:59 AM occurs twice that night) β€” a cron job scheduled for 30 2 * * * (2:30 AM) has two opportunities to "match" 2:30 AM that night β€” the first occurrence of 2:30 AM (before the fall-back), and the second occurrence (after the fall-back, when the clock reaches 2:30 again).

Depending on the implementation: the job might run twice, once at each "2:30 AM" β€” or the implementation might have specific logic to avoid double-execution (e.g., tracking "have I already run for "this" 2:30 today?" in a way that accounts for the repeated hour).

*A job not designed to be idempotent (covered in the previous "production scheduled jobs" article β€” idempotency meaning "running this multiple times has the same effect as running it once") running twice, on "fall-back" night, could cause duplicate processing β€” duplicate emails sent, duplicate records created, or other consequences of "this ran when it shouldn't have."


The "obvious" fix: UTC, and why it's the recommended approach for most automated systems

Configuring cron (and the underlying system) to operate in UTC, rather than a local, DST-observing timezone β€” eliminates both problems entirely β€” UTC doesn't observe DST β€” every day has exactly 24 hours, every hour occurs exactly once, every day, with no "skipped" or "repeated" hours ever.

A cron job scheduled in UTC (e.g., 30 2 * * * where the system's cron daemon operates in UTC) runs at exactly 2:30 UTC, every day, unconditionally β€” no DST-related edge cases exist, because UTC has no DST.

This is part of why server systems (and cloud infrastructure generally) are commonly configured to run in UTC, even for organizations/teams based in DST-observing timezones β€” separating "what timezone the server's clock/scheduler operates in" from "what timezone the humans using the system are in" β€” the server (and its cron jobs) operate in UTC; humans viewing "when did this run" logs can have UTC timestamps converted to their local timezone for display β€” but the underlying scheduling itself remains DST-immune.


When local time scheduling is intentional: "run at the start of the business day, wherever that is"

Sometimes, "run this job at local time" is, genuinely, the intended behavior β€” e.g., "send a daily summary email at 9 AM, the recipient's local time" β€” here, the whole point is that the job's trigger time should track "9 AM, local,"* including through DST transitions (the email should still arrive at "9 AM local" after a DST change, not at "the same UTC time as before the change," which would, post-DST-change, correspond to a different local time).

For such "intentionally local-time" scheduling β€” the DST-transition-night edge cases (skipped/doubled hour) are, to some extent, unavoidable given the fundamental nature of DST β€” but understanding that they will occur, roughly twice a year, on predictable dates (the specific DST-transition dates, which vary by country/region) β€” allows for deliberate handling: idempotency (so a "double-run" on fall-back night doesn't cause duplicate effects), and, for jobs where a "skipped" run (on spring-forward night) would be problematic β€” additional monitoring/alerting specifically checking "did this job run as expected, particularly around known DST transition dates."


Different countries' DST transitions happen on different dates

A further complication for systems serving users across multiple timezones/countries: DST transitions don't happen on the same date everywhere β€” different countries/regions (and, historically, even different jurisdictions within the same country, in some cases) have transitioned on different dates, and some regions don't observe DST at all.

For a system with "send at 9 AM, recipient's local time" jobs, serving recipients across multiple regions' local times β€” each region's "9 AM local" job needs to independently account for that region's own DST schedule (which, as noted, might differ from, or not exist at all for, other regions) β€” this is generally handled by timezone-aware scheduling libraries (which incorporate up-to-date timezone databases β€” the widely-used "tz database"/"IANA timezone database," which tracks historical, current, and (to the extent known in advance) future DST rules for every recognized timezone) β€” rather than naive "cron-expression-in-local-time" approaches, which typically operate relative to a single, system-wide timezone setting, not per-recipient timezones.


How to use the Cron Explainer on sadiqbd.com

  1. For most automated/server-side jobs: verify what timezone the cron daemon/system operates in β€” UTC is generally recommended unless there's a specific, intentional reason for local-time scheduling
  2. For jobs scheduled in the "2 AM hour" specifically (a common time for maintenance jobs, backups, etc. β€” chosen, often, precisely because it's "low-traffic" overnight): be aware this hour is exactly the hour most commonly affected by DST transitions (in regions observing DST with the typical "spring forward/fall back" occurring around this hour) β€” if operating in a local, DST-observing timezone, jobs in this window are the most likely to encounter the skipped/doubled-hour issues described
  3. For jobs requiring "local time, wherever the recipient is": consider whether a timezone-aware scheduling approach (per-recipient timezone handling, via a library incorporating the IANA timezone database) is more appropriate than a single, system-wide cron schedule

Frequently Asked Questions

If my server is configured for UTC, but I want a report generated at "9 AM New York time," how do I handle DST? You'd need to calculate, periodically (or dynamically), what UTC time corresponds to "9 AM New York time" β€” and this calculation's result changes across DST transitions (9 AM EST β‰  9 AM EDT, in UTC terms β€” they're 1 hour apart in UTC, even though both represent "9 AM, New York"). A naive, static cron schedule in UTC (e.g., 0 14 * * *, which is 9 AM EST in UTC) would correspond to 8 AM EDT (during daylight time), not 9 AM β€” a static UTC cron expression doesn't track "9 AM New York" through DST changes β€” for this, you'd need either (a) two different cron schedules, switched manually (or via automation) around DST transition dates, or (b) a timezone-aware scheduling library that computes the correct UTC trigger time dynamically, accounting for the target timezone's current DST status β€” option (b) is generally more robust and less error-prone than manually maintaining (a).

Is the Cron Explainer free? Yes β€” completely free, no sign-up required.

Try the Cron Explainer free at sadiqbd.com β€” translate any cron expression to plain English, and check scheduling against UTC.

Share:
Try the related tool:
Open Cron Explainer

More Cron Explainer articles