Skip to content
Linux Running · 7 min read

Daylight Saving Time on Servers: What Actually Breaks When the Clocks Change

Clocks went back tonight. What DST does to cron jobs, systemd timers, logs, databases, backups and game server restarts, and why my servers all run on UTC.

Tonight Europe did the thing again. At 03:00 the clocks jumped back to 02:00, so the hour between 2 and 3 happened twice. In spring it's the other way round and that hour just doesn't exist.

For you and me that's one extra hour of sleep. For a server that runs on local time it's an hour where "02:30" means two different moments, and in March it's an hour where "02:30" never comes at all. Most stuff survives it. Some stuff doesn't, and it usually fails quietly.

TL;DR: Run servers on UTC (timedatectl set-timezone UTC) and only convert to local time when a human looks at it. If a box has to stay on local time, keep scheduled jobs out of the 02:00–03:00 window, put UTC straight into systemd OnCalendar= lines, store timestamps with a timezone, and don't name backups by local hour. Game server restarts drift by an hour for players in other countries for about a week, because the US switches on a different date.

Check what you're running on

timedatectl

The interesting lines are Time zone: and RTC in local TZ:. If the time zone is anything other than Etc/UTC and the box runs scheduled jobs, read on.

To switch:

sudo timedatectl set-timezone UTC
sudo systemctl restart cron

Restart cron (or crond) afterwards. It reads the time zone when it starts, so without the restart it keeps scheduling on the old zone until the next reboot.

All my servers run on UTC. The daily backup of my Hytale server runs at 04:00 UTC, which is 05:00 or 06:00 in Germany depending on the season, and I don't have to think about it twice a year. That's the whole argument.

Cron: depends on which cron

Debian and Ubuntu ship Vixie cron with Debian's patches, and that one actually handles DST on purpose. From man 8 cron, in short:

  • Clock goes forward (spring): jobs that would have run in the skipped hour run right after the jump.
  • Clock goes back (autumn): jobs in the repeated hour are not run a second time.
  • This only applies to jobs with a fixed time. If the minute or hour field has a * in it, the job just runs on the new clock time.

So on Debian/Ubuntu:

30 2 * * *     /usr/local/bin/backup.sh     # fine, runs once, both in spring and autumn
*/10 2 * * *   /usr/local/bin/sync.sh       # wildcard minute: runs 12 times tonight instead of 6
0 * * * *      /usr/local/bin/hourly.sh     # runs at 02:00 twice, which is correct, it's hourly

The middle one is the trap. Looks harmless, but on the night the clock goes back it runs through the 2 o'clock hour twice.

Other crons are not that nice. I wouldn't assume BusyBox crond in an Alpine container, or whatever cron tool your app image ships, does the same thing. Check its docs or put the container on UTC, which most images are anyway unless someone set TZ.

Gotcha: CRON_TZ isn't everywhere

You'll find CRON_TZ=UTC at the top of crontabs on the internet. That works with cronie (Fedora, RHEL, Rocky, Alma). Debian's man 5 crontab doesn't mention it at all, so don't rely on it there. If you want UTC jobs on a box that stays on local time for some reason, use a systemd timer.

systemd timers: put the zone in the line

OnCalendar= uses the local time zone by default. But you can just write the zone at the end:

[Timer]
OnCalendar=*-*-* 02:30:00 UTC
Persistent=true

Then DST doesn't matter at all for that timer. Check what systemd thinks the next runs are before you trust it:

systemd-analyze calendar --iterations=3 '*-*-* 02:30:00 Europe/Berlin'
systemd-analyze calendar --iterations=3 '*-*-* 02:30:00 UTC'

It prints the next elapse times in local time and UTC, so you can see right away whether something lands in a weird spot. Persistent=true also catches the case where the box was off when the timer should have fired. More on timers in general in systemd timers instead of cron.

Logs: the hour that happens twice

If your logs have local timestamps without an offset, tonight's 02:00–03:00 is in there twice. Two lines both saying 02:41:07, one an hour after the other. Good luck putting an incident timeline together from that.

journalctl --utc --since "2026-10-25 00:00" --until "2026-10-25 02:00"

The journal stores everything internally in UTC anyway, --utc just shows it that way. For your own app logs, write ISO 8601 with offset (2026-10-25T02:41:07+01:00) or plain UTC. Then nobody has to guess which 02:41 it was.

Same with logrotate and anything that rotates "daily". A day with 25 hours is still one day, so usually nothing happens. But scripts that do "date minus 24 hours" to find yesterday's file get the wrong day once a year. Do date math in UTC or with real calendar functions. More on rotation in logrotate on Linux.

Databases: DATETIME has no idea

MySQL/MariaDB:

  • TIMESTAMP is stored as UTC and converted to the session time zone. Safe.
  • DATETIME is just a wall-clock value. 2026-10-25 02:30:00 in a DATETIME column is ambiguous tonight, and the database can't tell you which one it was.

PostgreSQL: timestamptz stores an absolute moment, timestamp (without time zone) is the same wall-clock trap as DATETIME.

If an app writes "now" in local time into a column without a zone, you get events that look like they happened in the wrong order, and durations that come out negative.

Backups named by local hour

tar -czf "/backup/site-$(date +%Y-%m-%d-%H).tar.gz" /var/www

If that runs hourly on local time, tonight the second 02 run writes the same filename as the first one and overwrites it. Not a disaster, you lose one hour. But in spring the 02 file is just missing and the retention script that expects 24 files a day might get upset.

Use UTC in the name:

tar -czf "/backup/site-$(date -u +%Y%m%dT%H%M%SZ).tar.gz" /var/www

Retention based on file age (find -mtime +14) doesn't care about DST, that part is fine. And if your job can run twice by accident, it should be safe to run twice. I wrote about that in idempotency in cron jobs.

Game servers: restarts move for half your players

This one isn't a bug on the server, it's a people problem. If your daily restart runs at 06:00 Europe time and you have players in the US, the restart moves for them twice a year. And for about a week it's extra confusing, because Europe and the US don't switch on the same day. Europe goes back on the last Sunday of October, the US on the first Sunday of November. This year that's October 25 and November 1. For that week New York is 5 hours behind Berlin instead of 6.

So for one week your "restart at midnight Eastern" announcement is wrong by an hour, unless the restart runs in UTC and you announce it in UTC (or with a Discord timestamp, which shows everyone their own local time).

The timers in Hytale server backups and auto-restart with systemd use plain times like 03:00. On a UTC box that's the same moment all year. On a box that runs on local time, add UTC to the line. If you send restart warnings into chat, generate the time from the same UTC value, don't hardcode "restart at 6".

Gotcha: dual boot and the hardware clock

Not a server thing, but it shows up every time DST changes. Windows keeps the hardware clock (RTC) on local time, Linux keeps it on UTC. Dual boot both and one of them is off by an hour or two after every boot. timedatectl shows RTC in local TZ: yes if Linux was set to match Windows.

The clean fix is telling Windows the RTC is UTC (the RealTimeIsUniversal registry value). The lazy fix is timedatectl set-local-rtc 1 on Linux, which timedatectl itself warns you about, because exactly this DST switch is when it goes wrong if Linux isn't running at the moment of the change.

Windows Task Scheduler

Same story, different UI. Scheduled tasks use local time by default. In the trigger settings there's "Synchronize across time zones", which anchors the trigger to UTC instead. For a backup task on a Windows Server I'd tick it, or at least keep the task out of the 02:00–03:00 window.

What doesn't care

So you don't panic about the wrong things:

  • TLS certificates, JWTs, Kerberos tickets: all Unix time / UTC. DST does nothing.
  • Prometheus and most monitoring stores timestamps as Unix time. Grafana just shows them in your browser's zone, so the graph tonight has a 25-hour day and that's correct.
  • NTP syncs UTC. The time zone is only how the number gets displayed.

My short checklist

  1. timedatectl on every box. Anything that isn't UTC and runs jobs, switch it and restart cron.
  2. crontab -l and /etc/cron.d/: anything with a wildcard in the 2 o'clock hour?
  3. systemd timers with UTC in OnCalendar=, verified with systemd-analyze calendar.
  4. App logs with offset or UTC.
  5. Timestamp columns with a time zone.
  6. Backup filenames with date -u.
  7. Game server announcements in UTC or as Discord timestamps.

The EU Parliament voted in 2019 to get rid of the clock change. It's 2026 and the clocks still change. So I'm not waiting for that, I'm putting everything on UTC.

If you're setting up a fresh box, the time zone is one of the first steps in my Ubuntu server initial setup checklist. Do it there and you never have to read a post like this one again.


Related posts