Set up the time zone and time synchronization on a Linux server

Published on 18 min read

A wrong server clock never shows up as a clock problem: it shows up as a rejected certificate, as a package source apt refuses, or as a cron job at the wrong hour. How to set the time zone and the time service correctly, and how to prove that synchronization is running.

Nobody pays any attention to a server clock as long as it is right. When it goes wrong, the clock never speaks up. What speaks up instead is a rejected certificate, a package source that apt refuses, a cron job at the wrong hour and a log file that no longer lines up with any other.

This article goes deeper into step 5 of the checklist for a new root server. If you have worked through the two commands there, you have done what is strictly required. This article is about everything beyond it: choosing the time service, the hardware clock, UTC versus local time, proving that synchronization works and the error messages that go with it.

Everything here applies to Debian 13 (trixie), Debian 12 (bookworm), Ubuntu 24.04 LTS and Ubuntu 22.04 LTS. The commands are written for use as root. If you work as a regular user, put sudo in front of every command.

What a wrong clock actually breaks

The common thread: none of these messages mentions the clock.

  • TLS certificates. Every certificate has a validity window. If the system time falls before it, curl reports curl: (60) SSL certificate problem: certificate is not yet valid, if it falls after it, certificate has expired. Every outgoing call is affected.
  • apt. Release files carry an issue date and an expiry date. If the clock runs behind, you get Release file for ... is not valid yet, if it runs well ahead, is expired. The server then stops receiving security updates without a single service going down.
  • The logs. Two machines that are five minutes apart can no longer be read against each other, and that is exactly when you need it: during a break-in or an outage.
  • Cron jobs and systemd timers. cron works in the system time zone. If you change the zone later, you shift every job. If the clock jumps, jobs run twice or not at all. Covered in detail in setting up a cron job.
  • Backups. Almost every backup names its folders after the date and deletes by age. A clock that jumps back one day overwrites yesterday's backup.
  • Time-based one-time passwords. TOTP works in 30-second windows, usually with a tolerance of one window on each side. If the server clock drifts further than that, every correct code is rejected. This is the one item on this list that actually locks you out.

Before you change anything: a way back and an inventory

The way back

Changing the time zone does not cut an open SSH session, and switching synchronization on is harmless as well. Two things in this article are not: a clock that jumps a long way, and switching the time service. A large jump backwards makes valid certificates temporarily invalid and throws TOTP logins out of their window. Switching the time service removes the old one before the new one is running, so if the installation breaks off in between, the server has no time synchronization at all, and that only becomes visible days later.

KVM root servers and dedicated servers from KernelHost have no IPMI and no iDRAC. The access path that works without any network service inside the guest is the VNC console in the customer panel. It hangs off the virtualization layer, or off the port itself, and a wrong clock inside the guest does not affect it. Log in there once beforehand and check that you know the root password.

The current state in four commands

Write down what applies right now. Without that note you will not be able to tell later whether a deviation is new:

timedatectl
readlink -f /etc/localtime
date -u
systemctl is-active systemd-timesyncd chrony

Of the seven lines that timedatectl prints, three matter. Time zone names the zone together with its abbreviation and offset, for example Europe/Vienna (CEST, +0200). System clock synchronized tells you whether the clock has ever been synchronized. NTP service has three states that are regularly mixed up:

LineMeaningWhat to do
NTP service: activeA time service is installed and running.Nothing, but verify it anyway.
NTP service: inactiveA time service is installed but not running.Start the service, look for the cause in the journal.
NTP service: n/aNo time service is installed at all.Install timesyncd or chrony.

In the third case timedatectl show -p CanNTP --value returns no, and every attempt to switch synchronization on ends with Failed to set ntp: NTP not supported.

Time zone and time synchronization are two different things

The kernel keeps exactly one counter: seconds since 1 January 1970, counted in UTC. That counter is the system time, and it knows nothing about time zones, because a zone is purely a matter of presentation. Two things follow from this. Changing the time zone does not move a single instant, date -u returns the same output before and after. And synchronizing the clock changes the counter, not the presentation, so a wrongly set zone stays wrong. Two tasks, two checks.

Step 1: set the time zone

Zone names come from the IANA database and almost always read Continent/City, in English spelling. Look the name up instead of guessing it:

timedatectl list-timezones | grep -i vienna
timedatectl set-timezone Europe/Vienna

Verifying the result:

timedatectl show -p Timezone --value
readlink -f /etc/localtime
date +%Z

What you expect is Europe/Vienna, the path /usr/share/zoneinfo/Europe/Vienna and an abbreviation that matches the season, CEST in summer and CET in winter.

Three pitfalls

The file /etc/timezone is not authoritative. Debian 13 no longer ships it, so a cat /etc/timezone ends with No such file or directory there, and tzdata does not bring it back either. On the other three systems it still exists, but on all of them the only thing that decides the zone is the symlink /etc/localtime.

Without tzdata there are no zones. Slim images, Ubuntu ones in particular, ship without the zone database. Then even a correctly spelled name fails with Failed to set time zone: Invalid or not installed time zone 'Europe/Vienna', and timedatectl list-timezones prints a single line. After the fix there have to be several hundred:

DEBIAN_FRONTEND=noninteractive apt-get install -y tzdata
timedatectl list-timezones | wc -l

Running services remember the old zone. cron, databases and application servers read the time zone at startup and keep logging in the old zone after a change until they are restarted.

In environments without a running systemd there is no timedatectl. The classic route leads to the same result:

ln -sf /usr/share/zoneinfo/Europe/Vienna /etc/localtime
dpkg-reconfigure -f noninteractive tzdata
readlink -f /etc/localtime

UTC or local time: a deliberate decision

Many operators run their servers on UTC, and there are good reasons for that. UTC has no daylight saving time, so there is no hour that occurs twice and none that is missing: on the night of the changeover, 02:30 local time exists twice in autumn and not at all in spring, which affects every job in that window. Against it stands the fact that people read the logs, and that a maintenance window of "Sundays 03:00" means local time. The rule that works: if you run several servers or several locations, take UTC, if you run a single server by the office clock, take local time. The only wrong answer is not knowing which one you have.

timedatectl set-timezone Etc/UTC

For a single look you do not have to touch the system time zone. The TZ variable is enough and applies only to that one call:

TZ=Europe/Vienna date
TZ=Europe/Vienna journalctl -u nginx --since "today"

Step 2: choose the time service

Debian and Ubuntu offer three candidates that solve the same task at very different scope.

ServiceWhat it can doWhen it fits
systemd-timesyncdPure SNTP client, queries one server at a time and moves on when that one failsThe normal case for a single server
chronyFull NTP client and server, combines several sources, ships a diagnostic tool with chronyc, supports NTSWhen you want to prove accuracy or track down errors
ntpsecSuccessor of the reference implementation ntpdExisting systems and special cases

All three declare the same role to the package system: Provides: time-daemon and at the same time Conflicts: time-daemon. If you ask for two of them in one command, you get a refusal:

The following packages have unmet dependencies:
 chrony : Conflicts: time-daemon
 systemd-timesyncd : Conflicts: time-daemon
E: Unable to correct problems, you have held broken packages.

If you install chrony while timesyncd is running, do not skip over the line The following packages will be REMOVED: systemd-timesyncd. That is intended, because two processes on the same clock are worse than none. Check right afterwards that the new service is running.

Two package names from older guides are history: ntp and ntpdate. On Debian 12 and both Ubuntu versions they are nothing but transitional packages pointing at ntpsec, and on Debian 13 apt-cache policy ntp simply reports Candidate: (none).

Option A: systemd-timesyncd

On all four systems the systemd package recommends systemd-timesyncd | time-daemon. A normal installation therefore brings the service along, a minimal image installed without recommends does not.

DEBIAN_FRONTEND=noninteractive apt-get install -y systemd-timesyncd
systemctl enable --now systemd-timesyncd

The shipped /etc/systemd/timesyncd.conf contains nothing but commented-out defaults. Your own values belong in a drop-in file, and its directory does not exist out of the box:

mkdir -p /etc/systemd/timesyncd.conf.d
cat > /etc/systemd/timesyncd.conf.d/10-kernelhost.conf <<'EOF'
[Time]
NTP=0.at.pool.ntp.org 1.at.pool.ntp.org 2.at.pool.ntp.org
FallbackNTP=0.pool.ntp.org 1.pool.ntp.org
EOF
systemctl restart systemd-timesyncd

NTP= holds the servers that get queried. FallbackNTP= only comes into play when none of them answers, and out of the box that is the Debian pool on Debian and ntp.ubuntu.com on Ubuntu.

Verifying the result:

systemd-analyze cat-config systemd/timesyncd.conf
systemctl is-active systemd-timesyncd
timedatectl timesync-status

The first command shows the assembled configuration, and your drop-in file has to appear in it with its full path. The third one names the server in use, the polling interval and the offset measured last. If it answers with Failed to query server: Connection timed out instead, timesyncd is not running. That message takes 25 seconds.

Option B: chrony

apt-get install -y chrony
systemctl enable --now chrony

On all four distributions the unit is called chrony.service and additionally carries the alias chronyd.service. The shipped /etc/chrony/chrony.conf already comes with usable defaults: Debian queries pool 2.debian.pool.ntp.org iburst, Ubuntu queries ntp.ubuntu.com plus three entries from the Ubuntu pool. Do not edit that file. Through confdir /etc/chrony/conf.d it pulls in a directory that package updates leave untouched:

cat > /etc/chrony/conf.d/10-kernelhost.conf <<'EOF'
pool 0.at.pool.ntp.org iburst
EOF
systemctl restart chrony

Verifying the result with chronyc tracking, this is what it looks like on a cleanly running server:

Reference ID    : A29FC801 (time.cloudflare.com)
Stratum         : 4
Ref time (UTC)  : Thu Sep 03 17:53:29 2026
System time     : 0.000135970 seconds fast of NTP time
Last offset     : +0.000042723 seconds
RMS offset      : 0.000086483 seconds
Frequency       : 13.967 ppm fast
Residual freq   : +0.002 ppm
Skew            : 0.073 ppm
Root delay      : 0.008282715 seconds
Root dispersion : 0.000613841 seconds
Update interval : 1038.4 seconds
Leap status     : Normal

Three lines are enough in daily use. System time is the current deviation, here roughly 136 microseconds. Leap status has to read Normal, and if it says Not synchronised, chrony does not have a usable source yet. Stratum tells you how far you are from a reference clock, 2 to 4 is normal. The second look goes to the sources themselves, with chronyc -n sources:

MS Name/IP address         Stratum Poll Reach LastRx Last sample
===============================================================================
^- 217.175.198.239               3  10   377   748   +114us[ +153us] +/-   19ms
^- 46.102.157.67                 2  10   377   558   +151us[ +192us] +/-   28ms
^* 162.159.200.1                 3  10   377   335   -251us[ -208us] +/- 4463us
^+ 152.53.132.244                2   9   377   231   +335us[ +335us] +/- 5626us

The two characters on the far left are the actual finding. ^* marks the source the clock is set by, ^+ one that is taken into account, ^- one that is not combined, ^? an unreachable one and ^x a contradictory one. The Reach column holds an octal value covering the last eight queries: 377 means all eight were answered, 0 means none arrived.

From version 4 onwards chrony supports Network Time Security, that is authenticated time over TLS. This requires two things that are easy to overlook: the ca-certificates package and an outgoing TCP port 4460. Without the root certificates the sync fails with Error in the certificate verification. The certificate is NOT trusted. The certificate issuer is unknown.

apt-get install -y ca-certificates
cat > /etc/chrony/conf.d/20-nts.conf <<'EOF'
server time.cloudflare.com iburst nts
EOF
systemctl restart chrony
chronyc authdata

The hardware clock

Next to the system time there is a second clock: the hardware clock, RTC for short. It supplies the first time value at boot, long before any time service is running. On a KVM root server that is the clock provided by the virtualization layer, and timedatectl shows it in the RTC time line. Exactly one setting matters, the last line of the output:

timedatectl set-local-rtc 0
timedatectl | grep "RTC in local TZ"

If it says yes, the hardware clock is read as local time. That is a concession to machines that also boot Windows, and it is pointless on a server. The damage shows up twice a year: at the daylight saving changeover, a hardware clock kept in local time is ambiguous for an hour, and a reboot inside that window can bring the server up with a time that is off by one hour.

In the other direction: through the rtcsync default, chrony regularly writes the corrected time back into the hardware clock, and that line is present in the shipped chrony.conf of all four distributions. systemd-timesyncd has no such option. By hand it works with hwclock --systohc. On Debian 12, Debian 13 and Ubuntu 24.04 the command comes from the util-linux-extra package; if the shell reports hwclock: command not found, install it. On Ubuntu 22.04 it belongs to util-linux.

Step 3: prove that synchronization is running

The fact that a command ran without an error is not proof. These five checks together are proof:

  1. The overall state. timedatectl shows System clock synchronized: yes and NTP service: active. Both lines together, not one of them.
  2. The service itself. systemctl is-enabled chrony and systemctl is-active chrony answer with enabled and active, and the same applies to timesyncd. enabled on its own only means that it would run at the next boot.
  3. A source is really being reached. With chrony, chronyc -n sources has to hold at least one line marked ^* and the Reach column has to have moved away from 0. With timesyncd, timedatectl timesync-status names a specific server.
  4. The offset is small. chronyc tracking should show microseconds or milliseconds at System time. Whole seconds mean that the correction is still under way or that something is stuck.
  5. It survives a reboot. Reboot the server once, then repeat checks 1 to 4. This is the only check that answers the question conclusively.

The line you must not trust on its own: System clock synchronized: yes reflects a flag in the kernel that was set by whichever process last adjusted the clock. It does not disappear when the time service crashes. A server can therefore show this line and still have been running for hours without any synchronization.

Services that strictly need a correct clock at startup are ordered with After=time-sync.target. For that target to be reached only after the clock has been synchronized, you also need a waiting unit: systemd-time-wait-sync.service with timesyncd, chrony-wait.service with chrony. Debian 12, Debian 13 and Ubuntu 24.04 ship the latter, Ubuntu 22.04 does not. How a unit of your own is put together is covered in creating a systemd service.

Firewall and network

NTP goes outbound over UDP port 123, and NTS adds TCP 4460. In the UFW default configuration outgoing traffic is allowed, so there is nothing to do. If you have set ufw default deny outgoing, you have to open the port, otherwise the clock stops being corrected without any service writing an error message:

ufw allow out 123/udp comment 'NTP'

The rest of the firewall topic is covered in setting up the UFW firewall. In the opposite direction: a time service that answers requests from the internet is an amplifier for reflection attacks. Out of the box, neither chrony nor systemd-timesyncd answers anyone, and only an allow line in the chrony configuration turns the client into a server. Set it scoped to your own network only. The upstream filtering in the maincubes datacenter in Frankfurt am Main catches traffic like that, but the best amplifier is still the one that does not exist.

When the clock is far off

Under normal conditions time services do not correct by jumping. They correct by speeding the clock up or slowing it down by a tiny amount. That is why a deviation of one minute does not disappear within a second, and that is exactly right: a jump backwards makes timestamps occur twice.

For freshly booted systems the shipped chrony.conf of all four distributions holds the line makestep 1 3: it permits a real jump when the offset is above one second, and only for the first three synchronizations. That is exactly the case of a virtual machine cloned from an image or restored from a snapshot. If that is not enough, force the jump and wait for the result:

chronyc makestep
chronyc waitsync 10

Measuring without changing anything works with the -Q switch: it queries the configured sources, reports the deviation and exits without touching the clock. The interesting line then reads something like System clock wrong by 0.000363 seconds (ignored):

chronyd -Q -f /etc/chrony/chrony.conf

What you should not do is set the clock by hand with date -s while a time service is running. timedatectl set-time refuses that explicitly with Failed to set time: Automatic time synchronization is enabled, because otherwise two instances would be turning the same clock at once.

Common errors and how to fix them

Failed to set time zone: Invalid or not installed time zone 'Europe/Wien': the zone name does not exist. The IANA database uses English city names, so it is Europe/Vienna, Europe/Zurich and Europe/Prague. If the same message comes up for a correctly spelled name, the zone database is missing: the counter-check is timedatectl list-timezones | wc -l, and if a single line is all that is left, install tzdata.

Failed to set ntp: NTP not supported: no time service is installed at all, and timedatectl show -p CanNTP --value then returns no. The set-ntp switch only controls services that have registered themselves under /usr/lib/systemd/ntp-units.d/: timesyncd with 80-systemd-timesync.list, chrony with 50-chrony.list.

timedatectl set-ntp true runs without an error, yet NTP service stays inactive: the command only reports that it has instructed the unit, not that the unit is running. The cause is in the journal, for example with journalctl -u systemd-timesyncd -n 20.

506 Cannot talk to daemon: chronyc cannot reach the service because chronyd is not running. systemctl status chrony gives you the reason. The message is the same for every chronyc subcommand.

chrony.service - chrony, an NTP client/server was skipped because of an unmet condition check (ConditionCapability=CAP_SYS_TIME).: the system is not allowed to set the clock at all. On container-based VPS (LXC, OpenVZ) this is the normal case, because the host system dictates the time there. On a KVM root server the guest is allowed to set its own clock, so there the message is a real finding. From the timesyncd side the same case looks like this: systemd-timesyncd.service - Network Time Synchronization was skipped because of an unmet condition check (ConditionVirtualization=!container).

Release file for ... is not valid yet during apt update: the clock is running behind. Fix the time first, then run apt update again. The other way round, is expired points to a clock that runs ahead, and less often to an outdated mirror.

Failed to query server: Connection timed out during timedatectl timesync-status: timesyncd is not running, or chrony has replaced it. If chrony is the active service, this command stays without an answer permanently, and that is not an error. The right tool is then chronyc tracking.

Distribution differences at a glance

ItemDebian 13Debian 12Ubuntu 24.04Ubuntu 22.04
chrony version4.6.14.34.54.2
Source in chrony.confDebian poolDebian poolntp.ubuntu.com plus Ubuntu poolntp.ubuntu.com plus Ubuntu pool
/etc/timezoneno longer presentpresentpresentpresent
hwclock from packageutil-linux-extrautil-linux-extrautil-linux-extrautil-linux
chrony-wait.serviceyesyesyesno
ntp and ntpdateno longer availabletransitional packagestransitional packagestransitional packages

What is the same everywhere: the time zone depends solely on the symlink /etc/localtime, and one time service rules out every other.

The short version

For a freshly provisioned root server that is meant to run on local time and gets by with the default service:

DEBIAN_FRONTEND=noninteractive apt-get install -y systemd-timesyncd tzdata
timedatectl set-timezone Europe/Vienna
timedatectl set-local-rtc 0
systemctl enable --now systemd-timesyncd
timedatectl

What you expect at the end is Time zone: Europe/Vienna, System clock synchronized: yes, NTP service: active and RTC in local TZ: no. If those four lines still read that way after a reboot, the clock of this server is taken care of.

Frequently asked questions

What breaks when the clock of a server is wrong?
The clock itself is never what you notice, only one of its consequences. Every TLS certificate has a validity window: if the system time falls before it, curl reports "curl: (60) SSL certificate problem: certificate is not yet valid", if it falls after it, "certificate has expired". apt rejects release files with "Release file for ... is not valid yet", and the server then stops receiving security updates without a single service going down. On top of that come logs that can no longer be read against each other, cron jobs at the wrong hour, backups that are named after the date and overwrite yesterday's, and time-based one-time passwords. TOTP works in 30-second windows with a tolerance of usually one window on each side, so a server clock that drifts further than that really does lock you out.
Does the time of the server change when I switch the time zone?
No. The kernel keeps exactly one counter, seconds since 1 January 1970 in UTC, and the time zone is purely a matter of presentation. The command date -u returns the same output before and after the change. The other way round: a synchronization changes the counter, not the presentation, so a wrongly set zone stays wrong. These are two tasks with two checks. Note as well that cron, databases and application servers read the time zone at startup and keep logging in the old zone until they are restarted.
Why does timedatectl report "Failed to set time zone: Invalid or not installed time zone"?
There are two causes for this. Either the name does not exist: the IANA database uses English city names, so it is Europe/Vienna and not Europe/Wien, and likewise Europe/Zurich and Europe/Prague. Or the zone database is missing, which happens on slim images, Ubuntu ones in particular. The counter-check is timedatectl list-timezones | wc -l: if a single line is all that is left, install tzdata, after which there have to be several hundred. Incidentally, the only thing that decides the zone is the symlink /etc/localtime, not the file /etc/timezone, which Debian 13 no longer ships at all.
systemd-timesyncd or chrony: which time service should I use?
systemd-timesyncd is a pure SNTP client, queries one server at a time and moves on to another when that one fails. That is the normal case for a single server. chrony is a full NTP client and server, combines several sources, ships a diagnostic tool with chronyc and supports Network Time Security, that is authenticated time over TLS. Take chrony when you want to prove accuracy or track down errors. Running both is not possible: every time service declares "Provides: time-daemon" and at the same time "Conflicts: time-daemon" to the package system. Installing chrony therefore removes systemd-timesyncd, which you can see in the line "The following packages will be REMOVED: systemd-timesyncd". Check right afterwards that the new service is running.
timedatectl set-ntp fails with "Failed to set ntp: NTP not supported". What is missing?
No time service is installed at all. In the output of timedatectl the NTP service line then holds the value n/a, and timedatectl show -p CanNTP --value returns no. The set-ntp switch only controls services that have registered themselves under /usr/lib/systemd/ntp-units.d/: timesyncd with 80-systemd-timesync.list, chrony with 50-chrony.list. Install one of the two. This is different from NTP service: inactive, where a service is present but not running, and where the cause is in the journal.
chrony does not start on my VPS and reports an unmet condition. Is that a real problem?
The journal then holds "was skipped because of an unmet condition check (ConditionCapability=CAP_SYS_TIME)", which means the system is not allowed to set the clock at all. On container-based VPS (LXC, OpenVZ) this is the normal case, because the host system dictates the time there. On a KVM root server the guest is allowed to set its own clock, so there the message is a real finding. From the timesyncd side the same case shows up with the condition ConditionVirtualization=!container.
How do I prove that time synchronization is really running?
The line System clock synchronized: yes is not enough on its own. It reflects a flag in the kernel that was set by whichever process last adjusted the clock, and it does not disappear when the time service crashes. So check five things: that NTP service: active stands next to it, that systemctl is-enabled and is-active report enabled and active for the service, that a source is really being reached (with chrony a line marked ^* in chronyc -n sources and a Reach column other than 0, with timesyncd a specific server in timedatectl timesync-status), that chronyc tracking shows microseconds or milliseconds at System time, and that all of it survives a reboot. Only the last check answers the question conclusively.
Should the server run on UTC or on local time?
If you run several servers or several locations, take UTC, if you run a single server by the office clock, take local time. What speaks for UTC is that it has no daylight saving time: on the night of the changeover, 02:30 local time exists twice in autumn and not at all in spring, which affects every job in that window. Against it stands the fact that people read the logs, and that a maintenance window of Sundays 03:00 means local time. The only wrong answer is not knowing which one you have. For a single look you do not have to touch the system time zone, TZ=Europe/Vienna date applies only to that one call. Independently of this decision, the hardware clock belongs on UTC: timedatectl set-local-rtc 0, and the output has to read RTC in local TZ: no.

Time zone NTP timedatectl chrony systemd-timesyncd Linux Debian Ubuntu