CronDrive
Guides

Cron and time zones: why a job runs at another hour

Updated 16 October 2026

Cron follows the schedule in the time zone of the system where it runs, which may not be yours. When a job set for 08:00 runs at 11:00, or an hour early after a change of season, the cause is usually here.

Which time zone cron uses

The system’s, as it was when cron started. To see it on the server:

timedatectl | grep "Time zone"
date

In a container, without timedatectl, date shows the time and the abbreviation of the zone. Compare it with the hour you expect: if the server is in UTC and you wrote the schedule in your local time, the difference is your offset from UTC.

Change the server’s time zone

sudo timedatectl set-timezone America/Santiago
sudo systemctl restart cron     # crond on RHEL, Fedora and Rocky

Restart cron after the change. On Debian and Ubuntu, the jobs see the new time right away, but cron keeps following the schedule in the old zone until it restarts.

Changing the server’s zone also changes the times in its logs and in the other programs that run there. The other option is to leave the server in UTC and write the schedule in UTC: in the cron tester, choose a time zone to see when an expression runs there.

They are different goals. A server in UTC has no daylight saving time, but a schedule fixed in UTC runs at another local hour when your zone changes its clocks. If the job must run at 08:00 local time all year, keep its schedule in the local zone, with one of the options below.

A time zone for one crontab

  • cronie (RHEL, Fedora, Rocky, AlmaLinux) accepts CRON_TZ at the top of the crontab. The lines below it follow that zone, and the logs keep the server’s time:

    CRON_TZ=America/Santiago
    0 8 * * 1-5 /path/to/report.sh
  • The cron of Debian and Ubuntu doesn’t. A TZ= line in the crontab only changes the time zone of the commands, not when they run. Its manual suggests running the job every hour and checking the time in the command. Check the day there too: the day fields are still read in the server’s zone, and when it’s already Saturday in UTC it can still be Friday in Santiago.

    TZ=America/Santiago
    0 * * * * [ "$(date +\%H)" = 08 ] && [ "$(date +\%u)" -le 5 ] && /path/to/report.sh

    date +%u gives the day of the week, from 1 (Monday) to 7 (Sunday), in the zone of TZ. The % are escaped with \: in a crontab line, a % alone ends the command.

Docker, Kubernetes and systemd

  • In a Docker container, cron uses the container’s zone, usually UTC. Install tzdata and set TZ when you start it: the details are in how to run cron jobs in a Docker container.

  • A Kubernetes CronJob takes its time zone in spec.timeZone, since Kubernetes 1.27. Without it, the schedule follows the zone of the cluster’s controller manager, usually UTC:

    spec:
      schedule: "0 8 * * 1-5"
      timeZone: "America/Santiago"
  • A systemd timer accepts the zone in OnCalendar. systemd-analyze calendar shows the next run, also in UTC:

    OnCalendar=Mon..Fri *-*-* 08:00:00 America/Santiago

Daylight saving time

The cron of Debian and Ubuntu, and cronie, handle a change of less than three hours in a special way, but only for the jobs that run at a specific hour:

  • When the clock moves forward, the jobs that fell in the skipped time run right after the change. A job at 02:30 in a zone that jumps from 02:00 to 03:00 runs at about 03:00.
  • When the clock moves back, the jobs that fall in the repeated time don’t run twice.
  • The jobs with * in the hour or the minute, like every 15 minutes or every hour, follow the new time as usual.

For a job that must run exactly once a day at a precise hour, avoid the hour of the change in your zone. If what matters is the hour in UTC, keep its schedule in UTC, which has no daylight saving time. Other schedulers, like systemd or Kubernetes, have their own rules: check their documentation.

Know when it runs at another hour

In CronDrive, choose the same time zone that the scheduler uses to read the cron expression: the server’s, the one in CRON_TZ, the container’s or the one in spec.timeZone. The form shows the current time in that zone, to compare it with date where the job runs. If the job then runs at another hour and falls outside the waits you set, CronDrive tells you that it didn’t start or didn’t finish on time.

CronDrive emails you, or posts to Slack, when a job with this schedule fails, or doesn't start or finish on time. Free for up to 15 jobs.

What you'd see with this schedule

An example with the last runs of this schedule and made-up results: one failed and one didn't run.

Times in UTC.

Due
Thu 15 Oct, 08:00 On time Started 08:00, finished 08:13, took 13 min.
Wed 14 Oct, 08:00 Didn't run Alerts sent at 08:05 and 08:30.
Tue 13 Oct, 08:00 On time Started 08:00, finished 08:14, took 14 min.
Mon 12 Oct, 08:00 Failed with code 1 Alert sent at 08:04. Started 08:00, finished 08:04, took 4 min.
Fri 9 Oct, 08:00 On time Started 08:00, finished 08:13, took 13 min.
Thu 8 Oct, 08:00 On time Started 08:00, finished 08:14, took 14 min.

The alert of the failed run

Email from CronDrive, Mon 12 Oct, 08:04

Failed (exit code 1): "Your job"

The job "Your job" failed with exit code 1.

If you connect Slack, the same message goes to your channel.

The change in your crontab

0 8 * * 1-5 curl -fsS -m 10 https://job.crondrive.com/uG2Exw/start/$$; /path/to/job.sh; curl -fsS -m 10 https://job.crondrive.com/uG2Exw/end/$?/$$

Two calls around your command, in the same line. Your job's line comes with its own token.

Monitor this schedule