Every night at two in the morning, a systemd timer on the backup server runs an rsync script pointed at the internal DNS server we built in Chapter 9. One morning, the Sysadmin found the backup folder filled with duplicate copies for the same date, while the previous day's archive had disappeared without a trace. After investigating, the cause turned out to be trivial: the backup server's system clock was eight hours behind the DNS server because it had never been synchronized since initial installation. As a result, the script that should have run once a day triggered multiple times and overwrote archives on the wrong date. Issues like this are usually only discovered when a needed backup is missing, right when production data is actually lost. The root cause is almost always the same: a server whose time accuracy was never maintained.
This chapter covers how to maintain time accuracy on Linux servers using chrony, starting from why this topic matters despite often being overlooked, verifying the status of chrony as the default NTP service on Ubuntu Server, configuring one internal server as a time source for other servers on the same network, to verifying synchronization results via chronyc tracking.
11.1 Why Accurate Time Matters on Servers
Every computer has a hardware clock that runs using an internal crystal oscillator. This component is never truly precise, causing the system clock to drift little by little from actual time, a phenomenon known as clock drift. On physical machines, the shift is usually small and slow. On virtual machines, however, this drift tends to be worse because virtual clocks rely on hypervisor scheduling, which can delay or pause vCPU execution without the guest operating system's knowledge. In the field, a VM recently restored from an old snapshot is one of the most common causes of system clocks suddenly drifting far off.
Without a correction mechanism, this drift accumulates continuously and has tangible impacts on server operations:
- TLS certificate validation: any browser or client application will reject HTTPS certificates whose Not Before or Not After dates make no sense compared to the system clock, even if the certificate itself is actually valid. We will cover this topic further when setting up Let's Encrypt in Chapter 17.
- Cross-server log correlation: as in the opening scenario, security incident investigations rely heavily on a consistent chronological order of events across all servers. Chapter 39 will cover centralized logging, and that effort is of little use if the timestamps from each source are out of sync.
- Time-based authentication protocols: Kerberos, one of the centralized authentication methods touched upon in Chapter 4, rejects authentication requests if the time difference between client and server exceeds a certain tolerance threshold (typically around 5 minutes) as protection against replay attacks.
- Reliability of services we built: DHCP lease durations in Chapter 10 and DNS cache Time To Live (TTL) values in Chapter 9 are both calculated based on the system clock. An incorrect clock causes both mechanisms to behave unexpectedly, such as considering a lease expired when it is not, or vice versa.
- Automated scheduling: systemd timers from Chapter 5 and scheduled backup scripts in later chapters rely entirely on an accurate system clock to run tasks on time.
The industry standard solution for this problem is NTP (Network Time Protocol), a protocol that enables a computer to align its system clock with a more accurate time source over the network. Before diving into configuration, let us check the server's current time state first.
date
timedatectl statusPay attention to the System clock synchronized and NTP service lines in the output of timedatectl status. Both lines serve as quick indicators of the server's synchronization status before we dive deeper into chrony as the NTP implementation used by Ubuntu Server.
11.2 Chrony as the Default NTP Client in Ubuntu Server
chrony is an NTP implementation consisting of two main components: chronyd, the daemon running in the background to continuously adjust the system clock, and chronyc, the command-line interface for checking status and managing chronyd. According to official Ubuntu Server documentation, chrony is installed by default for automatic time synchronization starting from Ubuntu 25.10, and that status is maintained in Ubuntu Server 26.04 LTS, which serves as the reference for this series. This step replaces the old combination of ntpd or systemd-timesyncd that still frequently appears in older tutorials, because chrony has proven to achieve synchronization faster and is more accurate at handling networks with unstable connections. Both characteristics are critical for production servers.
11.2.1 Verifying Chrony Installation and Service Status
Since chrony is already installed by default, our first step is not installation, but verifying that its service is actually running.
Practical Steps
- Ensure the
chronypackage is indeed installed.
If it turns out not to be installed, for example due to a minimal installation that removed it, install the package manually.dpkg -l chronysudo apt update sudo apt install chrony - Check the status of
chronydthrough its systemd service.systemctl status chrony.service - Reconfirm via
timedatectlthat the system recognizeschronyas the active NTP synchronization provider.timedatectl status
Verification and Troubleshooting
- Ideally, the output of
timedatectl statusshowsSystem clock synchronized: yesandNTP service: active. If either still showsnoorinactive, explicitly enablechrony.sudo systemctl enable --now chrony.service - If
timedatectlinstead showssystemd-timesyncdas the active NTP provider, it indicates both services are competing for control over the system clock. Disablesystemd-timesyncdto avoid conflicts withchrony.sudo systemctl disable --now systemd-timesyncd.service
11.2.2 Configuration Structure and Network Time Security (NTS)
The main chrony configuration resides in /etc/chrony/chrony.conf. Since recent releases, Ubuntu separates the list of NTP sources into its own directory under /etc/chrony/sources.d/, rather than writing them directly in chrony.conf. The default file is /etc/chrony/sources.d/ubuntu-ntp-pools.sources, which contains:
pool 1.ntp.ubuntu.com iburst maxsources 1 nts prefer
pool 2.ntp.ubuntu.com iburst maxsources 1 nts prefer
pool 3.ntp.ubuntu.com iburst maxsources 1 nts prefer
pool 4.ntp.ubuntu.com iburst maxsources 1 nts prefer
pool ntp-bootstrap.ubuntu.com iburst maxsources 1 nts certset 1Notice the nts flag on each line. Since Ubuntu enabled NTS (Network Time Security) by default, all communication to the Ubuntu NTP pool is encrypted and authenticated using TLS, rather than plain UDP packets like classic NTP. This is important from a security perspective because traditional NTP is vulnerable to spoofing. An attacker on the same network could forge NTP responses to shift the target clock to an incorrect time, for instance to make an expired TLS certificate appear still valid. NTS closes this vulnerability through a key negotiation process over port 4460/TCP, while the time data exchange itself continues over port 123/UDP as with standard NTP. The prefer flag indicates that chronyd prioritizes that source when multiple sources are equally reachable.
Verification and Troubleshooting
- View the list of time sources currently used by
chronydalong with their selection status.
Thechronyc -N sources^*symbol in the leftmost column marks the source currently selected as the primary reference, while^+marks acceptable candidate sources that have not yet been selected. - Ensure NTS negotiation succeeds by checking authentication data for each source.
Successful sources showsudo chronyc -N authdataMode: NTSwith non-zeroKeyIDandKLenvalues. If the values remain zero after a few minutes, there is likely a firewall on your network blocking port 4460/TCP for NTS key negotiation.
11.3 Configuring a Server as an Internal NTP Source
While having every server query public NTP pools individually still functions, it is less than ideal for an internal network that already has multiple servers, such as the DNS server from Chapter 9 and the DHCP server from Chapter 10 that we built. Ideally, only one internal server synchronizes with public pools, and then other servers and devices on the same network synchronize with that internal server. This approach offers several clear advantages: latency to the time source is significantly lower because it resides on the local network, internet requests are drastically reduced, and equally important, the internal server can continue providing consistent time to the entire local network even during internet connection outages.
In this practice, we will configure the same server used for the internal DNS server in Chapter 9 (IP 192.168.1.10) as the internal NTP source for the 192.168.1.0/24 network, maintaining consistency with the network scheme used since Chapters 9 and 10.
11.3.1 Allowing Client Access with the allow Directive
By default, chronyd rejects all external NTP requests, even when running as a synchronized client. For this server to serve time requests to other devices, we need an allow directive that explicitly specifies which subnets are permitted.
Practical Steps
- Edit
/etc/chrony/chrony.confon the server acting as the internal NTP source.sudo nano /etc/chrony/chrony.conf - Add the following
allowdirective at the end of the file.allow 192.168.1.0/24 - Also add the
local stratum 10directive if we want this server to continue serving time to the local network even when its connection to the internet pool is disconnected. This directive makeschronyd"pretend" to be synchronized from the perspective of requesting clients, even if its own upstream source is unavailable.
It should be emphasized that time shared vialocal stratum 10localmode is not truly accurate relative to the outside world once upstream remains disconnected for a prolonged period; it merely maintains time consistency among devices within the local network itself. Do not rely on this mode as a permanent replacement for a legitimate NTP source; use it only as a temporary safety net. - Because
allowandlocalare access and server behavior directives (not just time source lists insources.d/), apply the changes by performing a full restart, not justreload sources.
This restart causes a very brief pause insudo systemctl restart chrony.servicechronyd's own synchronization process. The impact is usually imperceptible, but it should still be performed outside peak hours on production servers. - Open access for NTP ports in UFW and restrict it strictly to the relevant internal subnet so this server does not serve NTP requests from the internet arbitrarily.
sudo ufw allow from 192.168.1.0/24 to any port 123 proto udp
Verification and Troubleshooting
- After several clients begin synchronizing with this server (see Section 11.3.2), check the list of clients that have requested time from this internal server.
chronyc clients - If clients still cannot access this server, the most common cause is that the subnet in the
allowdirective does not match the actual client subnet, or port 123/UDP is not opened in UFW as in the last step above.
11.3.2 Directing Clients to the Internal NTP Server
The internal server is now ready to serve time requests. The next step is directing other servers on the same network to synchronize with this server, rather than directly with public pools.
Practical Steps
- On each client server, create a new time source file in the
sources.d/directory instead of editing the defaultubuntu-ntp-pools.sourcesdirectly.sudo nano /etc/chrony/sources.d/internal-ntp.sources - Populate the file with a
serverdirective pointing to the internal NTP server's IP address, along with theiburstflag for faster initial synchronization and thepreferflag to prioritize it over default public pools.
We intentionally do not delete defaultserver 192.168.1.10 iburst preferubuntu-ntp-pools.sourcesso that public pools remain available as a fallback if the internal NTP server goes down. Thepreferflag is sufficient to makechronydprioritize the internal server as long as it is reachable. - Apply this new time source without needing to fully restart
chronyd.sudo chronyc reload sources
Verification and Troubleshooting
- Check whether
chronydon the client has selected the internal server as its primary source.
The line with addresschronyc -N sources192.168.1.10should be marked with^*in the leftmost column, indicating that the server is currently actively used as the reference. - If the internal server has not been selected despite being listed in the sources, wait a few minutes as
chronydrequires several polling cycles to evaluate a source's quality before deciding to use it, even with thepreferflag.
11.4 Verifying Synchronization with chronyc tracking
After configuring both server and clients, chronyc tracking is the most important command for ensuring synchronization is actually functioning as expected, rather than just being an active systemd service with no real result.
Practical Steps
- Run the following command on both the internal NTP server and its clients.
The output will look similar to this:chronyc -N trackingReference ID : B97DBE7B (2.ntp.ubuntu.com) Stratum : 3 Ref time (UTC) : Mon Jun 16 13:06:04 2025 System time : 0.000000004 seconds slow of NTP time Last offset : +0.001758954 seconds RMS offset : 0.017604901 seconds Frequency : 3.889 ppm slow Residual freq : +0.202 ppm Skew : 1.458 ppm Root delay : 0.022837413 seconds Root dispersion : 0.003050051 seconds Update interval : 1032.5 seconds Leap status : Normal
The following lines are most commonly paid attention to during verification or troubleshooting:
- Reference ID: the identity of the time source currently in use. On our internal server, this value should point to one of the Ubuntu pools, whereas on a client, it should point to the IP address of the internal NTP server we just configured.
- Stratum: the distance of the time source from a physical reference clock (atomic clock or GPS located at stratum 0). A server connected directly to stratum 0 is called stratum 1. Clients synchronizing with our internal server are automatically one stratum higher than the server itself, e.g., stratum 4 if the internal server is at stratum 3.
- System time: the offset of the system clock relative to actual NTP time at the moment, reported in seconds. The closer to zero, the more accurate the time.
- Root delay and Root dispersion: total network delay and total accumulated uncertainty across all hops to stratum 0. Small values indicate the path to the time source is short and stable.
- Leap status: ideally shows
Normal. A value ofNot synchronizedmeanschronydhas not yet successfully synchronized with any source, which is the clearest sign that something needs investigation.
Verification and Troubleshooting
- To view the quality and reachability history of each source in greater detail, use the following command:
chronyc sourcestats - Pay attention to the
Reachcolumn in the output ofchronyc -N sources, which is printed in octal notation. A value of377means 8 out of 8 polls were successfully answered by that source, while significantly lower values indicate the source frequently fails to respond, due either to network issues or target server problems. - If
Leap statuspersistently showsNot synchronizedon a client, the most efficient inspection sequence is: ensure the internal server itself isNormalfirst, verify theallowdirective on the server includes the client subnet, then ensure port 123/UDP is unblocked on both sides. - One important note for container environments covered in Chapters 26 and 27: Docker containers share the kernel clock with the host, so running a separate
chronydinstance inside each container is unnecessary and uncommon. Simply ensure the host machine running those containers is properly synchronized.
Up to this point, we have understood why time accuracy is not just a cosmetic detail on servers, but a core foundation for TLS, authentication, logs, and services built in previous chapters. We have also practiced verifying chrony as Ubuntu Server's default NTP client, building an internal server as a time source for the local network, and interpreting chronyc tracking output to confirm everything is synchronized properly. Chapter 12 will pivot to a new topic serving as the foundation for Part IV: setting up our first web server using Nginx.

