NTP doesn’t actually care about the time it takes to send a packet, it cares about the time the packet arrives.

Let’s watch NTP in action. Imagine you have a client machine client.example.com and a server ntp.example.com.

On ntp.example.com (assuming it’s already configured to serve time, maybe via ntpd or chronyd):

# On the NTP server, listen for requests. This is usually handled by the daemon,
# but for illustration, we can see it listening on UDP port 123.
sudo ss -lunp | grep 123
# Output might look like:
# udp   UNCONN 0      0      0.0.0.0:123      0.0.0.0:*     users:(("chronyd",pid=1234,fd=5))

On client.example.com, we can use ntpdate to query the server directly:

# This command asks ntp.example.com for the current time.
sudo ntpdate -q ntp.example.com

The -q flag tells ntpdate to just query the server once and exit, without trying to set the local clock. The output will show you the offset:

server 192.168.1.100, stratum 3, offset -0.001234, delay 0.056789
15 Mar 10:30:00 ntpdate[5678]: adjust time server 192.168.1.100 offset -0.001234 sec

Here’s what’s happening:

  1. Client sends request: ntpdate on client.example.com sends a UDP packet to ntp.example.com:123. It records the exact time it sent this packet (T1).
  2. Server receives request: ntp.example.com receives the UDP packet and records the exact time it arrived (T2).
  3. Server processes and replies: The NTP server looks up its own time (T3) and sends a response packet back to the client.
  4. Client receives reply: client.example.com receives the response packet and records the exact time it arrived (T4).

The NTP client then uses these four timestamps (T1, T2, T3, T4) to calculate the network delay and the time difference between the client and server. The core calculation is roughly:

Offset = ((T2 - T1) + (T3 - T4)) / 2 Delay = (T4 - T1) - (T3 - T2)

The server’s stratum indicates how far it is from an authoritative time source (like an atomic clock or GPS receiver). Stratum 0 are the reference clocks, stratum 1 servers are directly connected to stratum 0, and so on.

NTP’s primary goal is to correct for network latency and clock drift to ensure all machines agree on the current time. It does this by sending small UDP packets, measuring the round-trip time, and using sophisticated algorithms to filter out erroneous measurements and account for asymmetric network delays. The protocol is designed to be robust even on unreliable networks. It’s not just about "getting the time"; it’s about a continuous, distributed process of clock synchronization.

What most people miss is that NTP can also operate in a symmetric active or symmetric passive mode. In symmetric active mode, two NTP servers exchange packets periodically, treating each other as potential clients and servers. This is how larger NTP constellations maintain internal consistency. The server that is considered more authoritative (lower stratum, better clock quality) will influence the other. Symmetric passive mode is similar but involves less active polling and more passive monitoring. This allows for a hierarchical and resilient time distribution network where servers can back each other up.

The next thing you’ll encounter is the challenge of configuring NTP clients to talk to multiple servers and how to select the "best" one.

Want structured learning?

Take the full Udp course →