Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Chrony and NTP: synchronise a server's clock

By updated

Improve this page

Chrony is the NTP client and server of current Linux distributions: it aligns the clock on time servers, with millisecond-range accuracy over the Internet, and can encrypt the exchange with NTS. A correct clock underpins log correlation, the validity of TLS certificates and every distributed system. This guide installs chrony, shows where its configuration lives depending on the distribution, teaches you to read chronyc tracking and chronyc sources, enables NTS, then opens the service to a local network. Audience: Linux administrators. Every output was recorded on 30 September 2026, with chrony 4.6.1 (Debian 13), 4.5 (Ubuntu 24.04) and 4.8 (AlmaLinux 10).

What you will learn

  • Install chrony and check that it really replaces the time service already present.
  • Find the configuration file, which does not sit in the same place in every family.
  • Read the synchronisation state with chronyc tracking and chronyc sources.
  • Encrypt the time exchange with NTS and check that it works.
  • Serve time to a local network without opening the service to everyone.

Prerequisites

  • A Debian 13, Ubuntu 24.04 or RHEL 10 server (or AlmaLinux, Rocky), with sudo rights.
  • Outbound Internet access on port UDP 123 (NTP), and TCP 4460 for NTS.
  • Knowing how to start a service and read its state with systemctl.

Why a server's clock must be right

A drifting clock does not bring a server down: it skews everything that compares dates. Two logs thirty seconds apart make an incident impossible to reconstruct, a TLS certificate looks "not yet valid" or "expired", a time-limited authentication token is rejected. Every hardware clock drifts, and the clock of a virtual machine also depends on the host that runs it.

The NTP protocol (Network Time Protocol), designed by David L. Mills and deployed as early as 1985, corrects this drift by querying time servers. Chrony is its modern implementation: started in 1997 by Richard Curnow and maintained by Miroslav Lichvar, it locks on faster than the old ntpd at boot, copes better with network outages, and it is the one the reference distributions offer today.

Key concepts

Five notions are enough to read everything chrony prints. They come back in every output of chronyc, and mixing them up leads to wrong diagnoses.

Stratum

The stratum measures the distance to the reference clock. Stratum 0 is the physical clock itself (atomic, GPS), which does not speak NTP. A server directly connected to it is stratum 1, a server locked on a stratum 1 is stratum 2, and so on. Your server therefore shows the stratum of its source plus one.

Sources, servers and pools

A source is a server being queried. The server directive declares one, the pool directive declares a DNS name hiding several servers: chrony keeps up to four of them by default. Distributions ship a pool (2.debian.pool.ntp.org, 2.almalinux.pool.ntp.org), which gives several sources without writing anything.

Drift and driftfile

The drift is the rate difference between the local clock and real time. Chrony measures it continuously and records it in the driftfile (/var/lib/chrony/chrony.drift on Debian, /var/lib/chrony/drift on AlmaLinux): after a restart, it starts again with an already known correction instead of learning it again.

Slew or step: makestep

By default, chrony corrects smoothly (slew): it speeds up or slows down the clock until the gap is closed, never going backwards, which preserves the order of logs and transactions. The makestep 1 3 directive, present in all three default configurations, allows an immediate step if the gap exceeds 1 second, and only during the first 3 updates: that is the case of a machine left switched off for a long time.

iburst

The iburst option, after server or pool, sends a burst of requests at startup instead of waiting for the normal interval. The first lock thus takes a few seconds instead of several minutes. It is set on every source of the default configurations.

Install chrony

On Debian and Ubuntu, installing chrony uninstalls systemd-timesyncd, the minimal time client shipped by default on Ubuntu. Both packages provide the same virtual service, time-daemon, and exclude each other: APT says so plainly on Ubuntu 24.04. On RHEL and its derivatives, chrony is the standard time service.

Terminal window
sudo apt update
sudo apt install chrony
The following packages will be REMOVED:
systemd-timesyncd

The service starts as soon as it is installed. It is called chrony.service, with an alias chronyd.service: both names work with systemctl.

Then check that the service is running and that the clock is synchronised, two different things:

Terminal window
systemctl status chronyd
chronyc tracking

Configuring chrony

The configuration file does not have the same path in every family, and it is the first source of mistakes. Recorded on the three reference distributions:

FamilyConfigurationKeysDrop-in directories
Debian 13, Ubuntu 24.04/etc/chrony/chrony.conf/etc/chrony/chrony.keys/etc/chrony/sources.d/, /etc/chrony/conf.d/
RHEL 10, AlmaLinux 10/etc/chrony.confnone by defaultnone by default

Editing /etc/chrony.conf on a Debian means writing a file that chronyd will never read: the configuration looks in place, and nothing changes. When in doubt, ask systemd which daemon runs and with which options:

Terminal window
systemctl cat chronyd

The default configuration

Here is the configuration shipped by Debian 13, comments removed. AlmaLinux 10's holds the same essential directives, with the pool 2.almalinux.pool.ntp.org.

pool 2.debian.pool.ntp.org iburst
sourcedir /run/chrony-dhcp
sourcedir /etc/chrony/sources.d
keyfile /etc/chrony/chrony.keys
driftfile /var/lib/chrony/chrony.drift
ntsdumpdir /var/lib/chrony
logdir /var/log/chrony
maxupdateskew 100.0
rtcsync
makestep 1 3
leapseclist /usr/share/zoneinfo/leap-seconds.list
confdir /etc/chrony/conf.d

The directives to know: pool (the sources), sourcedir (sources added as files, or provided by DHCP in /run/chrony-dhcp), driftfile (the stored drift), ntsdumpdir (the NTS key cache, which avoids renegotiating at every restart), rtcsync (the kernel regularly copies the system time into the hardware clock) and makestep.

Declare your own sources

On Debian and Ubuntu, the clean method is to add a file in /etc/chrony/sources.d/ rather than edit the main file: it survives package updates. The file must end in .sources and only contain server, pool or peer lines, and a simple reload is enough, without restarting the service:

Terminal window
echo 'server ntp.internal.lan iburst' | sudo tee /etc/chrony/sources.d/internal.sources
sudo chronyc reload sources

On RHEL and AlmaLinux, which do not have this directory, add the server line to /etc/chrony.conf, then restart:

Terminal window
sudo systemctl restart chronyd

Declare at least three sources. With two sources that disagree, chrony cannot tell which one lies; from three onwards, it discards the one that drifts away from the others.

Check the synchronisation

An active chronyd service does not prove the time is right. It can run without any source answering, for instance if port UDP 123 is filtered, and the clock then drifts without a single error message. Two commands provide the proof.

chronyc tracking: the state of the clock

chronyc tracking tells you which source the system is locked on and what gap it measures. Output recorded on AlmaLinux 10, a few seconds after startup:

Reference ID : A29FC801 (time.cloudflare.com)
Stratum : 4
Ref time (UTC) : Wed Sep 30 14:44:15 2026
System time : 0.000812048 seconds slow of NTP time
Last offset : -0.000666413 seconds
RMS offset : 0.000666413 seconds
Frequency : 0.000 ppm slow
Residual freq : -196.961 ppm
Skew : 1000000.000 ppm
Root delay : 0.026948174 seconds
Root dispersion : 31.631998062 seconds
Update interval : 2.0 seconds
Leap status : Normal

The lines that matter for diagnosis:

  • Reference ID: the source in use. A value of 00000000 means no source.
  • Stratum: that of the source plus one. A stratum 10 usually means a server that fell back on its local clock (local stratum 10) because no source is reachable.
  • System time: the current gap, here 0.8 millisecond slow.
  • Skew and Root dispersion: the uncertainty. The huge values of this output are normal right after startup, they melt away within minutes.
  • Leap status: Normal when all is well, Not synchronised when the clock is locked on nothing.

chronyc sources: the servers queried

chronyc sources lists the sources and tells which one is in use. With the -v option, chrony prints its own legend above the table:

.-- Source mode '^' = server, '=' = peer, '#' = local clock.
/ .- Source state '*' = current best, '+' = combined, '-' = not combined,
| / 'x' = may be in error, '~' = too variable, '?' = unusable.
|| .- xxxx [ yyyy ] +/- zzzz
|| Reachability register (octal) -. | xxxx = adjusted offset,
|| Log2(Polling interval) --. | | yyyy = measured offset,
|| \ | | zzzz = estimated error.
|| | | \
MS Name/IP address Stratum Poll Reach LastRx Last sample
===============================================================================
^+ time.cloudflare.com 3 6 17 22 +1316us[+1806us] +/- 16ms
^* gbg2-ts.nts.netnod.se 1 6 17 22 -1400us[ -911us] +/- 22ms
^? ptbtime1.ptb.de 1 7 21 14 +1912us[+1912us] +/- 18ms

The first character gives the mode (^ for a server), the second the state, and that is the one to look at:

SymbolMeaning
*the best source, the one currently driving the clock
+source combined with the best one to refine the computation
-selectable source, not combined, nothing abnormal
xsource chronyd considers wrong (falseticker)
~source whose time is too variable to be used
?unusable source: unreachable, or not yet evaluated enough

The Reach column is an octal register of the last eight requests: 377 means eight answers out of eight, 17 four answers after a recent start, 0 none. Poll is the base-2 logarithm of the interval: 6 means 64 seconds. A healthy clock shows at least one ^* line and a Reach climbing towards 377.

Encrypt the time exchange with NTS

NTS (Network Time Security, RFC 8915) authenticates NTP answers: an attacker on the network can no longer supply a false time without being detected. Classic NTP has no protection at all, and shifting a server's clock is enough to make it accept an expired certificate. Chrony has supported NTS since version 4.0, and the three versions recorded here ship it.

Enabling it takes one option, nts, on servers that offer it. The chrony.conf manual recommends, as soon as you authenticate, to remove unauthenticated sources: in the default mix mode, chrony only uses them if they agree with the NTS sources. So replace the pool with at least three NTS servers:

server time.cloudflare.com iburst nts
server nts.netnod.se iburst nts
server ptbtime1.ptb.de iburst nts

On Debian and Ubuntu, put these lines in a .sources file under /etc/chrony/sources.d/ and comment out the pool line of the main file; on AlmaLinux, replace the pool line of /etc/chrony.conf. Then restart the service.

chronyc authdata proves NTS works. Recorded with the three servers above:

Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen
=========================================================================
time.cloudflare.com NTS 1 30 128 27 0 0 8 64
gbg2-ts.nts.netnod.se NTS 1 15 256 27 0 0 8 100
ptbtime1.ptb.de NTS 1 15 256 27 0 0 8 100

The Cook column counts the cookies received when the session was set up: 8 means the negotiation succeeded. A 0 with an Atmp (attempts) count that keeps rising signals a failed setup, which the chronyd log explains.

Serve time to the local network

A single internal server locked on the Internet, and every machine on the network locked on it: that is the most common architecture. By default, chrony serves time to nobody: you must allow it explicitly with allow, on the internal server.

allow 192.168.10.0/24

If you want the server to keep answering when the Internet is down, add local stratum 10: it then serves its own clock, with a high stratum that warns clients of its low quality. Finally open port UDP 123 in the firewall, for that network only, and declare the server on the client side with a server line.

Securing chrony

Chrony opens two separate channels, and most security mistakes come from confusing them. The NTP service (UDP 123) distributes time; the command channel (UDP 323 and a Unix socket) lets chronyc drive the daemon.

DirectiveChannelEffect
allow / denyNTP servicewho may receive time
cmdallow / cmddenycommandswho may drive chronyd remotely
bindcmdaddresscommandsthe listening address for commands

By default, the command channel only listens on 127.0.0.1 and ::1, which is the right setting: do not open it to the network with cmdallow, or you expose the daemon's administration while believing you opened time. A bindcmdaddress 127.0.0.1 line only makes this default explicit.

Symmetric keys between server and internal clients

To authenticate an internal server that does not offer NTS, chrony accepts symmetric keys, shared between the server and its clients. Generate them with chronyc keygen, which produces a random SHA256 key, never a key typed by hand:

Terminal window
chronyc keygen 1 SHA256 256
1 SHA256 HEX:1866637433DF91090E2D5F7E346120C2B803FB091A12BD7464B528194A66CC22

Copy this line into the key file on both sides (/etc/chrony/chrony.keys on Debian, a file declared by keyfile on AlmaLinux), then add key 1 to the client's server line. The key file keeps the permissions shipped by Debian: owner root, service group (_chrony), mode 640. Chronyd drops root privileges after startup: recorded on Debian 13, a file in root:root 600 is indeed loaded at launch, but the log shows Missing read access, and a chronyc rekey then fails with Could not open keyfile. A 644 mode, conversely, exposes the keys to every local account.

Troubleshooting

SymptomLikely causeSolution
chronyc sources only shows ^? and a Reach of 0Port UDP 123 blocked outbound, or DNS unreachableTest with chronyc -n sources, open UDP 123 towards the servers
Reference ID : 00000000 and Leap status : Not synchronisedNo usable sourceCheck the sources, then the firewall
Stratum : 10 on an internal serverFell back on local stratum 10, Internet unreachableRestore access to the external sources
The edited configuration has no effectWrong file (/etc/chrony.conf on Debian)systemctl cat chronyd, then edit the right file
NTS source with Cook 0 in chronyc authdataca-certificates missing, or TCP 4460 blockedInstall ca-certificates, open TCP 4460 outbound
Internal clients receive no timeNo allow, or deny all written after allowA single allow <network>, without deny all
A directive copied from an old tutorial has no effectRemoved directive, such as commandkey, ignored with a mere warningRead journalctl -u chronyd, remove the reported line

The service log, journalctl -u chronyd, gives the exact cause of an ignored directive, an unreadable key file or an NTS failure.

Hands-on practice

Nothing replaces hands-on work: this lab has you configure chrony, check the synchronisation with chronyc tracking and set the time zone, all of it persistently on a VM.

Key points

  1. An active chronyd proves nothing: only chronyc tracking and a ^* line in chronyc sources prove the synchronisation.
  2. The configuration lives in /etc/chrony/chrony.conf on Debian and Ubuntu, in /etc/chrony.conf on RHEL: editing the wrong file changes nothing.
  3. Installing chrony on Debian or Ubuntu removes systemd-timesyncd: one time service per machine.
  4. NTS authenticates time with the nts option; it needs ca-certificates and port TCP 4460, and is checked with chronyc authdata.
  5. Chrony serves time to nobody by default: an allow <network> is enough, and a deny all written after it shuts everything.

Knowledge check

Check that the essentials of this guide are acquired. The questions only cover what has just been explained here.

Knowledge check

Check what you have learned with this interactive quiz

6 questions
6 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

More information

The project documentation is the reference for every directive, and the NTP site traces the history of the protocol.

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn