Keep Server Time Accurate with chrony on an Ubuntu VPS

Ubuntu uses chrony to keep the system clock in sync, and chrony is installed by default as of Ubuntu 25.10. On older releases systemd-timesyncd was the default, and chrony takes one apt command to install.


Where Clock Errors and Time Jumps Show Up

TLS certificates

Every X.509 certificate carries a validity period, from notBefore to notAfter. Path validation checks that this period includes the current time, and "current time" means whatever the server's clock says. A VPS running days behind can reject a certificate issued yesterday as not yet valid. A VPS running far ahead can treat a valid certificate as expired.

Secure time sync has the same dependency. Network Time Security (NTS) is based on TLS, and TLS needs a reasonably correct clock, so an NTS-based sync can fail when the clock is too far off. Ubuntu's default setup handles this with a bootstrap server.

Logs

chronyd tracks the drift and offset of the system clock and adjusts it continuously, so there are no large corrections that could lead to inconsistent logs.

cron

Ubuntu's cron handles local time changes of less than three hours in a special way. The special handling covers only jobs that run at a specific time and jobs with a granularity greater than one hour. cron schedules jobs that run more often than that in the normal way.

  • After a forward jump, such as one hour, the covered jobs that would have run in the skipped interval run immediately.
  • After a backward jump, cron avoids running the same job twice.

cron treats a change of more than three hours as a correction to the clock or the timezone and uses the new time immediately. On a VPS whose clock is corrected by four hours, neither rule above applies.



The Commands

These are the shell commands used in this guide. The makestep and burst commands change chronyd's behaviour, so they run through sudo chronyc. waitsync is a monitoring command and runs without sudo.

 1# install
 2sudo apt install chrony
 3
 4# check status
 5timedatectl status
 6chronyc -N tracking
 7chronyc -N sources
 8chronyc sourcestats
 9sudo chronyc -N authdata
10
11# after editing sources or config
12sudo chronyc reload sources
13sudo systemctl restart chrony.service
14systemctl status chrony.service
15
16# step a large offset, then wait for sync
17sudo chronyc makestep 0.1 1
18sudo chronyc burst 1/2
19chronyc waitsync 60 0.01

On Ubuntu 25.10 and later, /etc/chrony/sources.d/ubuntu-ntp-pools.sources contains these default sources:

1pool 1.ntp.ubuntu.com iburst maxsources 1 nts prefer
2pool 2.ntp.ubuntu.com iburst maxsources 1 nts prefer
3pool 3.ntp.ubuntu.com iburst maxsources 1 nts prefer
4pool 4.ntp.ubuntu.com iburst maxsources 1 nts prefer
5pool ntp-bootstrap.ubuntu.com iburst maxsources 1 nts certset 1

Install chrony on Ubuntu

sudo apt install chrony installs two binaries. chronyd is the daemon that syncs time over the Network Time Protocol (NTP) and can also serve it to other machines. chronyc is the command-line interface to that daemon. The daemon runs as user and group _chrony.

Some systems were upgraded from a release that used timesyncd. On those, systemd-timesyncd might still be the active time daemon and leave the new chrony service disabled. Run systemctl status chrony.service and confirm that chrony is running. Once chrony is installed, timedatectl steps back and leaves timekeeping to chrony.

chrony serves time to other machines only after you set at least an allow rule. A VPS that only needs its own clock to be right needs no such rule.


Check Sync Status

timedatectl status gives the quick answer. On a synced server it shows System clock synchronized: yes and NTP service: active. For the detail behind that answer, run chronyc -N tracking.

Some chronyc commands need sudo and some do not. chronyc first tries the Unix domain socket at /var/run/chrony/chronyd.sock, and sudo gives it access to that admin socket. Without the socket, chronyc can use only the commands chronyd accepts over the network: activity, manual list, rtcdata, smoothing, sourcename, sources, sourcestats, tracking and waitsync. By default chronyd accepts those only from localhost (127.0.0.1 or ::1), and chronyc uses UDP port 323 for them. Every other command in this guide, including authdata, reload sources and makestep, needs sudo.

Three options change how the output reads:

OptionEffect
-nNo reverse lookups of IP addresses, which avoids slow DNS queries
-NEach source shown by the hostname or IP address written in the configuration file
-cReports in comma-separated values (CSV), easier for a monitoring script to parse than the aligned table

Read chronyc tracking

The tracking command shows how well the system clock is performing. On Ubuntu the first lines name the source and its stratum, for example Reference ID : B97DBE7B (2.ntp.ubuntu.com) and Stratum : 3. These fields tell you whether the clock is right:

  • Reference ID: the source chronyd is synced to. 7F7F0101 with no name or IP address means the server is not synchronised to any external source.
  • Stratum: how many hops the server is from a computer with an attached reference clock.
  • System time: the current offset between the NTP clock and the system clock. chronyd normally corrects it by speeding the clock up or slowing it down, up to the rate set by the maxslewrate directive. This is called slewing. A large offset takes a very long time to correct this way.
  • Last offset: a positive value means the local time was ahead of the time sources.
  • Frequency: how fast the clock would drift if chronyd stopped correcting it, in parts per million (ppm).
  • Update interval: the time between the last two clock updates.
  • Leap status: Normal, Insert second, Delete second or Not synchronised.

The same output gives an upper bound on the clock's error:

clock_error <= |system_time_offset| + root_dispersion + (0.5 * root_delay)

Root delay is the total network path delay back to the stratum-1 computer, and root dispersion is the dispersion accumulated along that chain. The bound assumes the stratum-1 computer itself is correct.


Read chronyc sources and sourcestats

chronyc -N sources lists the time sources in use. Each row starts with two characters. The first is the mode: ^ means a server, = a peer and # a locally connected reference clock. The second is the selection state:

SymbolMeaning
*The best source, currently selected for synchronisation
+Another selected source, combined with the best one
-Selectable, but not selected now
xA falseticker, in chronyd's judgement
~Time with too much variability
?Not selectable for another reason, such as being unreachable, not synchronised, or short of measurements

On a healthy VPS one row starts with ^*, a server that chronyd has selected. If no row has a *, the clock has no selected source.

Poll gives the measurement interval as a power of two in seconds, so 6 means a measurement every 64 seconds. A Reach of 377 means a valid reply came back for each of the last eight transmissions. Any other value means at least one of those eight got no valid reply. In Last sample, a positive offset means the local clock is ahead of the source. The number after +/- is the margin of error, the NTP root distance.

chronyc sourcestats is the companion report. For each source, chronyd estimates the drift rate and current offset by fitting a linear regression through its samples.


Confirm NTS Is Working (Ubuntu 25.10 and Later)

Ubuntu 25.10 and later turn NTS on by default through the Ubuntu NTS pools. NTS support itself arrived in chrony 4.0, which first appeared in Ubuntu 21.04. The pools are defined in /etc/chrony/sources.d/ubuntu-ntp-pools.sources. That file holds four ntp.ubuntu.com pools marked nts prefer and one ntp-bootstrap.ubuntu.com pool. With iburst on each line, chronyd starts with a burst of 4-8 requests, so the first clock update comes sooner.

The bootstrap server lets a system with a large clock offset still adjust its clock securely. It covers the TLS problem described above. For a clock too far off even for that, the nocerttimecheck option exists.

NTS needs two ports. NTS/KE uses its own port, 4460/tcp, and normal NTP still uses 123/udp. If either is blocked on the VPS or at the provider's firewall, chrony cannot adjust the system clock with the default configuration. Open both ports. If you cannot, edit /etc/chrony/sources.d/ubuntu-ntp-pools.sources to use the listed NTP servers instead of the NTS ones.

To check the session, run sudo chronyc -N authdata. Its columns are Name/IP address, Mode, KeyID, Type, KLen, Last, Atmp, NAK, Cook and CLen. A working NTS source shows NTS in Mode and non-zero values for KeyID, Type and KLen. SK in Mode means a symmetric key, and - means authentication is off. A - on a 25.10 server means the source is running plain NTP.


Fix a Large Offset

chronyd slews by default because a step in the system time might have adverse effects on certain applications. Slewing a large offset takes a very long time, so chronyc gives you two ways to step the clock instead.

makestep with no arguments cancels any remaining slew correction and jumps the system clock by that amount. The clock is correct immediately.

With two arguments, makestep sets up automatic stepping, like the makestep directive in chrony.conf. The first argument is the stepping threshold in seconds. The second is the number of future clock updates for which that threshold applies. Pair it with burst to take a new measurement quickly and step if needed. You do not have to wait for chronyd's normal measurement and update cycle. makestep 0.1 1 steps the clock if the next update is off by more than 0.1 seconds. burst 1/2 then makes chronyd measure every source over a short period, asking for one good measurement from each and trying at most two times.

A step of less than three hours falls under the cron rules above. A larger step does not, and cron uses the new time immediately.

The makestep directive in /etc/chrony/chrony.conf controls stepping at start-up. On most systems you want the clock to step only at boot, before programs that expect time to move forward steadily start. makestep 0.1 3 steps the clock if the adjustment is larger than 0.1 seconds, but only in the first three clock updates. Check whether your chrony.conf has a makestep line before you rely on boot-time stepping.

For a one-shot sync, use chronyd -q. To check the time once without setting it, use chronyd -Q.


Change Time Sources

Time sources live in *.sources files under /etc/chrony/sources.d/. After you add or remove sources, run sudo chronyc reload sources. chronyd then re-reads every *.sources file in the directories named by the sourcedir directive.

Defaults such as sourcedir, ntsdumpdir and rtcsync are set in /etc/chrony/chrony.conf. Put your own settings in separate files under /etc/chrony/conf.d/. After changing any config file, restart chrony with sudo systemctl restart chrony.service. Then check it with systemctl status chrony.service.

chrony also uses time sources handed out by DHCP (option 42). If your provider's DHCP gives out time servers you do not want, comment out this line in /etc/chrony/chrony.conf and restart:

1# sourcedir /run/chrony-dhcp

Gate Services on Sync with waitsync

waitsync lets a start-up script hold off until the clock is right. waitsync 60 0.01 waits up to about 10 minutes (60 tries, 10 seconds apart by default) for chronyd to sync to a source with less than 10 milliseconds of correction left. The first argument is the maximum number of tries. When chronyd runs out of tries, waitsync gives up and returns a non-zero error code.

A boot script can run chronyc waitsync 60 0.01 and stop on a non-zero exit. Services that check certificates or write timestamps then do not start on a wrong clock. waitsync is one of the monitoring commands, so it runs without sudo.


References

Posts in this series