Skip to main content
RunBook Academy

LinuxXXIV · Time Synchronisationchrony

chrony architecture - the modern NTP client and server

Intermediate⏱ ~12 minchronycchronyd

What you'll learn

  • Describe chrony sources and selection
  • Configure chrony as an NTP client
  • Configure chrony as an NTP server for a private network
  • Read chronyc tracking and sources output

Prerequisites

Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-09

Not yet marked complete on this device.

chrony is the recommended NTP client on modern RHEL, Rocky, Alma, Fedora, and is increasingly used on Debian and Ubuntu. It is more accurate than the older ntpd, handles intermittent connectivity better, and can act as both client and server. This lesson is about configuring it correctly.

chrony vs the alternatives

ToolWhen to use
chronydModern default on most distributions; client + server
systemd-timesyncdLightweight NTP client; not a server
ntpdLegacy; still used on older systems; client + server

For new deployments, chrony is the default choice.

Configuration

/etc/chrony.conf (RHEL family) or /etc/chrony/chrony.conf (Debian):

# Use public NTP servers
pool 2.pool.ntp.org iburst
pool time.cloudflare.com iburst

# Use the company NTP server
server ntp1.example.com iburst prefer
server ntp2.example.com iburst

# Allow this host to serve NTP to the local network
allow 10.0.0.0/24

# Drift file (records clock frequency offset)
driftfile /var/lib/chrony/drift

# Make stepping corrections instead of slewing for large
# offsets (default is to slew up to 1 second)
makestep 1.0 3

# Send NTP requests even when the network is offline
# (useful for laptops)
rtcsync

# Log file
logdir /var/log/chrony

# Stratum for this server (if it is one)
local stratum 10

Apply:

sudo systemctl enable --now chronyd
sudo systemctl restart chronyd

Read the state

chronyc tracking

Output:

Reference ID    : C0A80A01 (10.0.0.1)
System time     : 0.000001234 seconds fast of NTP time
Last offset     : +0.000000456 seconds (estimated)
RMS offset      : 0.000012345 seconds
Frequency       : 23.456 ppm (fast)
Residual freq   : +0.001 ppm
Skew            : 0.123 ppm
Root delay      : 0.012345 seconds
Root dispersion : 0.001234 seconds
Update interval : 64.2 seconds
Leap status     : Normal

Reading this:

  • Reference ID: which source is currently selected.
  • System time: difference between the system clock and the selected source. <1ms is good.
  • Last offset: most recent offset measurement.
  • Frequency: how far the system clock is running from true time, in ppm. A small value is good.
  • Skew: estimated error in the frequency.
  • Root delay: round-trip time to the source.
  • Root dispersion: accumulated error from the source.

Sources

chronyc sources

Output:

MS Name/IP address         Stratum Poll Reach LastRx Last sample
===============================================================================
^* ntp1.example.com              2   6   377    35   +12us[ -23us] +/-  85us
^- ntp2.example.com              2   6   377    34   -45us[ -67us] +/-  92us
^- time.cloudflare.com           3   6   377    33   +120us[+98us] +/-  210us
^- 2.pool.ntp.org                2   6   377    32   +156us[+142us] +/-  180us

Reading the flags:

  • * = current sync source (the one being used).
  • + = acceptable; combined with the selected source.
  • - = acceptable but not selected.
  • ? = unreachable or rejected.
  • x = rejected (by the algorithm).
  • ~ = too variable.

^* is the source your clock is synced to. The other acceptable sources are in case the primary fails.

Source selection

chrony uses a complex algorithm to select the best source. Key factors:

  • Stratum: lower is better. Stratum 0 is a hardware reference clock; stratum 1 is a server directly connected to one; stratum 2 is synced to stratum 1; etc.
  • Reach: a bit field showing recent reachability. 377 (octal) means all 8 recent attempts succeeded.
  • Last sample: offset and error estimate.

If ^* switches between sources, you have a configuration issue (unstable network, sources disagree, etc.).

chrony as a server

To serve NTP to a private network:

# In /etc/chrony.conf
allow 10.0.0.0/24
local stratum 10

The local stratum 10 directive makes this host a stratum 10 server that does not sync to the internet - useful for isolated networks.

For a server that syncs to public NTP and serves to the private network:

pool time.cloudflare.com iburst
allow 10.0.0.0/24

Clients on the private network configure their chrony to use this server.

So open the port as well as the ACL:

# firewalld (RHEL, Fedora, SUSE)
sudo firewall-cmd --permanent --add-service=ntp
sudo firewall-cmd --reload
sudo firewall-cmd --list-services      # confirm 'ntp' is listed

# nftables (Debian, Ubuntu, or any default-deny baseline)
sudo nft add rule inet filter input ip saddr 10.0.0.0/24 udp dport 123 accept
sudo nft list chain inet filter input  # confirm the rule is above any drop

# ufw
sudo ufw allow from 10.0.0.0/24 to any port 123 proto udp

Confirm chronyd is bound and listening:

sudo ss -ulnp | grep ':123'
# UNCONN 0 0 0.0.0.0:123 0.0.0.0:* users:(("chronyd",pid=...,fd=...))

Then verify from a client, never from the server. A server-side test proves nothing, because the loopback path never traverses the filter. On a client, chronyc sources -v must show Reach 377 against this server before you call the setup complete.

Authentication

chrony supports NTP authentication via symmetric keys or public keys (NTS, RFC 8915):

# Symmetric key: the key file holds ID/algorithm/secret
keyfile /etc/chrony/chrony.keys

# key, trust and require are OPTIONS on the source line
server ntp1.example.com iburst key 1 trust require

On chrony 4.0 and later, prefer NTS for anything crossing an untrusted path. There is no shared secret to distribute:

server ntp1.example.com iburst nts

For a private network, authentication prevents rogue NTP servers from poisoning your clocks.

Common recipes

Accurate time from public servers:

pool time.cloudflare.com iburst
pool ntp1.example.com iburst
makestep 1.0 3
rtcsync

Internal NTP server:

server ntp1.example.com iburst
server ntp2.example.com iburst
allow 10.0.0.0/24
local stratum 10

Stratum 1 server with GPS:

The refclock syntax is refclock driver parameter [option].... Pick one driver per line. gpsd can feed chrony either by shared memory (SHM) or by Unix socket (SOCK); the two are different drivers and must never be mixed on one line.

Shared memory, which is what gpsd writes by default:

# gpsd writes SHM segment 0 (NMEA time) and segment 1 (PPS)
refclock SHM 0 refid GPS precision 1e-1 offset 0.128 delay 0.2 noselect
refclock SHM 1 refid PPS precision 1e-7 prefer
allow 10.0.0.0/24

The socket protocol gpsd also speaks:

refclock SOCK /run/chrony.ttyS0.sock refid GPS
allow 10.0.0.0/24

The noselect on the NMEA segment is deliberate: NMEA sentences are accurate to tens of milliseconds, so they label the second while PPS supplies the precision. The offset 0.128 value is the serial-line delay for your receiver — measure it, do not copy it.

Initial sync after a long outage:

sudo systemctl stop chronyd
sudo chronyd -q 'server time.cloudflare.com iburst'
sudo systemctl start chronyd

The -q flag tells chrony to set the time and exit - useful after a long downtime when the clock is far off.

Knowledge check

Knowledge check · 4 questions

  1. Q1. Which chronyc command shows the current NTP source and timing offset?

  2. Q2. chrony can act as both an NTP client and an NTP server.

  3. Q3. Which of the following are valid chrony configuration directives? Select all that apply.

  4. Q4. You add authentication to /etc/chrony.conf with a `server ntp1 key 1` line followed by a separate `trust 1` line, restart chronyd, and walk away. An hour later Kerberos logins start failing across the fleet. What is the most likely cause?

Passing score: 75%. Answers are checked in this browser.