DST Ends Sunday. Find the Agent Jobs That Will Shift an Hour Before They Do.

On Sunday, November 1, most of the United States falls back an hour. At 2:00 a.m. the clock jumps back to 1:00 a.m., and the hour between 1:00 and 2:00 happens twice.

For people, that is an extra hour of sleep. For a self-hosted agent stack, it is the one night a year when every scheduled job quietly tells you which clock it was really using. Some jobs will keep running at the time you expect. Some will start an hour early or late. A few will skip once or run twice, depending on where they live and when they were set to fire.

None of this throws an error. The morning digest just shows up at 6 a.m. instead of 7. The client report goes out before the data it summarizes has landed. The “post at 8” job posts at 7, a full hour before the job that checks it.

You can find all of this in about 30 minutes, before Sunday instead of after. This post walks through how.

Why one weekend breaks things that worked all year

Most self-hosted agent setups use more than one scheduler, and they do not agree on what time it is:

  • System cron on the box or Pi usually runs in the machine’s local time zone.
  • systemd timers use local time by default, unless the calendar expression names a zone.
  • Hosted schedulers such as GitHub Actions cron run in UTC and do not move for daylight saving at all.
  • Your agent runtime’s own scheduler may use the host zone, a zone in its config, or UTC, depending on how it was set up.
  • Container jobs often run in UTC because the image has no zone data, even when the host is set to local time.

All year long these disagreements are invisible, because the offset between local time and UTC stays fixed. You set the UTC job one hour ahead to line it up with the local one, and it just works. On Sunday the offset changes from five hours to six for Central time, and every job pinned to UTC moves an hour in local terms while every job pinned to local time stays put. Jobs that were meant to run in sequence can now run out of order.

That ordering problem is what hurts. A single job running an hour off is usually harmless. A pipeline where the writer, the publisher, and the checker are on different clocks is not.

The three failure shapes

When you audit, sort each scheduled job into one of these.

1. It moves an hour

Any job scheduled in UTC will run an hour earlier in local time after the change (for US zones in November). If a person reads its output, or another job depends on it, check whether an hour earlier still works.

We hit this ourselves. MarketMai’s daily post was built on a Pi in local time, and the hosted workflow that checks it runs in UTC. To keep both near 8 a.m. Central all year, the hosted side now runs as a DST-safe pair: two UTC triggers an hour apart, with the job written so the second run is a harmless no-op if the first already did the work.

2. It skips or doubles

Jobs scheduled at a fixed local time inside the repeated hour, anything between 1:00 and 1:59 a.m. on the night of the change, are the ones to worry about. Classic Vixie-derived cron, which is what most Debian and Raspberry Pi OS systems run, documents that when the clock moves back by less than three hours, fixed-time jobs in the repeated hour are not run a second time. Jobs with a wildcard in the hour field, like “every 15 minutes,” keep running on real elapsed time and will fire during both copies of the hour.

Other schedulers behave differently, and some are not documented clearly. Do not assume. Check the man page for the cron you actually run (man 8 cron on most Linux systems), and for anything you cannot confirm, move it out of the 1 a.m. hour. That is the fastest fix there is.

In spring the problem flips. The 2:00 to 2:59 a.m. hour does not exist on that night, and depending on the scheduler, jobs set inside it either run right after the jump or do not run at all. If you are moving jobs anyway, move them out of the 1 a.m. to 3 a.m. window entirely and you will not have to think about either change again.

3. It runs on time but reads the wrong window

This is the sneaky one. The job fires at the right local time, but its logic says “process everything from the last 24 hours” or “yesterday’s data.” On a fall-back day, the local day is 25 hours long. A report built on “the last 24 hours” will miss an hour of data. A deduplication check that remembers “what I did in the last 24 hours” can repeat an action from 24.5 hours ago.

If your agent does anything with “yesterday,” “today,” or “the last day,” find out whether it calculates those in local time or UTC, and make sure the job that writes the data and the job that reads it agree.

The 30-minute audit

You do not need a new tool for this. You need a list.

Step 1: List every scheduler (5 minutes)

Write down every place a job can be scheduled. On a typical self-hosted stack that means:

  • crontab -l for your user and for root
  • systemctl list-timers --all
  • your agent runtime’s own job list or config
  • any hosted workflows (GitHub Actions, a hosted automation tool, a cloud scheduler)
  • cron or schedule settings inside containers

If you have never made this list, this step alone is worth the time. Most people find at least one job they forgot about.

Step 2: Write down each job’s zone (10 minutes)

For each job, record three things in a plain table: what it does, when you think it runs in local time, and which clock it actually uses.

Do not guess the third column. Check it:

  • Run timedatectl on each host to see the system zone.
  • Run date inside each container you schedule from. If it says UTC and you expected local, you found one.
  • For hosted workflows, assume UTC unless the docs for that service say otherwise.
  • For your agent runtime, look for a timezone setting in its config. If there is none, find out what it falls back to.

Step 3: Mark the dependencies (5 minutes)

Draw arrows between jobs that depend on each other. “Writer runs, then publisher runs, then checker runs.” “Data sync runs, then the report runs.” For each arrow, ask whether both ends are on the same clock. If not, that arrow will move by an hour on Sunday.

Step 4: Fix the cheap ones now (10 minutes)

For most jobs, one of these fixes is enough:

  1. Move it out of 1 a.m. to 3 a.m. local. Nothing important should be scheduled there.
  2. Put both ends of a dependency on the same clock. Either both in local time or both in UTC. Mixed pairs are where the bugs live.
  3. Pin the zone explicitly. If your scheduler supports a zone in the schedule (many do, through a TZ or CRON_TZ setting), name it instead of relying on the host default.
  4. Use a DST-safe pair for UTC-only schedulers. Two triggers an hour apart, with the job checking whether today’s work is already done before doing anything. That check has to be real: look for the output, not just a log line.
  5. Compute windows from timestamps, not from “24 hours ago.” “Since the last successful run” is more reliable than “the last 24 hours” on any day with a clock change.

The last two fixes are also what make jobs safe to rerun after an outage, which is the same problem in a different costume. If you have not yet decided what each job should do after the server was asleep, the missed-run semantics post covers that decision in detail.

What to check on Monday morning

Do not trust the audit until the change has actually happened. On Monday, November 2, spend five minutes confirming:

  • Every daily job ran exactly once on Sunday and once on Monday. Count the runs in the logs; do not just look for the latest one.
  • Each job’s Monday run started at the local time you expected.
  • Anything customer-facing, such as posts, emails, and reports, went out at the usual local time.
  • Any report covering Sunday has the full day of data, not 24 of its 25 hours.

If a job has no log line you can count, that is a separate problem worth fixing. A scheduled job that cannot prove it ran is one you will be guessing about every time something goes wrong. Writing down what each job promises, including when it runs and what proof it leaves, is the core of giving your agent a service contract instead of just a schedule.

The habit that makes this a non-event next time

The clock changes twice a year. The cheapest way to stay ahead of it is to keep the job table from Step 2 somewhere you will see it again, with a zone column that is always filled in. Add a line to it every time you schedule something new. Then put two reminders on your calendar, one for the week before each change, to reread it.

When every job has a known clock, a known window, and a known proof of running, daylight saving time stops being a mystery outage and becomes a five-minute check.

Turn the audit into a standing prompt

If you want your agent to keep that job table current for you, start with MarketMai’s free AI prompt pack and adapt one into a weekly “list every scheduled job, its zone, and its proof of running” check. When you are ready to run more of your business on agents, the MarketMai Ultimate Bundle puts 13 prompt, automation, and agent-operations products in one $49 download.

More from the build log

Suggested

Want the full MarketMai stack?

Get the core MarketMai guides and operator playbooks in one premium bundle for $49.

View Bundle