Overview: why build your own Stratum 1#

Why#

When a computer asks the Internet for the time, it usually queries a public NTP server, often a “Stratum 2” or “Stratum 3” pool, itself synchronized from other servers, synchronized from other servers… all the way up, somewhere, to an atomic clock or a GPS receiver. Each level of this chain is called a stratum: the closer to the physical source, the lower the number, and the higher the precision.

   Stratum 0       Stratum 1       Stratum 2       Stratum 3
   (physical    (directly linked  (synced to      (synced to
    clock)       to Stratum 0)    Stratum 1)      Stratum 2)

       ▼               ▼               ▼               ▼
     [GPS] ─────────► [S1] ─────────► [S2] ─────────► [S3]
    ns to µs        < 1 µs          < 1 ms          < 10 ms

A Stratum 1 server is directly attached to a physical reference, here, a GNSS receiver (GPS/Galileo/GLONASS/Beidou). It’s the highest precision achievable without a dedicated atomic clock (which costs tens of thousands of euros). With consumer-grade hardware (~€250) and careful configuration, precision around a microsecond is achievable, more than enough for the vast majority of everyday uses (server synchronization, logging, home or lab networks). This server remains a proximity time source, with no vocation to serve as a certified reference for critical infrastructure, financial transactions, or safety systems (see the site’s legal notice).

Why build it yourself instead of using a public NTP pool? Because it’s an excellent project for understanding Linux real-time internals, time metrology, and hardening a service exposed on the Internet in depth, and because a direct GPS receiver removes any network uncertainty between the user and the time reference, and in order to offer this service to the whole Internet through the NTP pool project.

How: the full chain, at a glance#

              ┌───────────────────────────────────────────────────────┐
              │  GNSS constellations (GPS, Galileo, GLONASS, Beidou)  │
              └───────────────────────────────────────────────────────┘
                                          │
                                          ▼ radio waves
                   ┌────────────────────────────────────────────┐
                   │  Antenna + GNSS receiver (u-blox NEO-M9N)  │
                   └────────────────────────────────────────────┘
                                          │
              ┌───────────────────────────┴───────────────────────────┐
              │ UART                                              PPS │
              │ (position + approx. time)          (second tick, ~ns) │
              ▼                                                       ▼
   ┌────────────────────┐                                      ┌─────────────┐
   │        gpsd        │                                      │   kernel    │
   │  (resolves second  │                                      │  (PPS IRQ,  │
   │     ambiguity)     │                                      │  FIFO 99)   │
   └────────────────────┘                                      └─────────────┘
              │                                                       │
              └───────────────────────────┬───────────────────────────┘
                                          ▼
                                    ┌───────────┐
                                    │  chronyd  │  ← disciplines the system clock
                                    └───────────┘    from the PPS, filters out noise
                                          │
                      ┌───────────────────┴───────────────────┐
                      │                                       │
                      ▼                                       ▼
                system clock                               NTP/NTS
              (CLOCK_REALTIME)                       (UDP 123, TCP 4460)
                                                          → clients

The PPS (Pulse Per Second) is the centerpiece: a physical electrical pulse, generated by the GNSS receiver on every exact second, with precision on the order of tens of nanoseconds. It’s what enables precision around a microsecond, the NMEA/UBX data stream (position, date) is only used to resolve which second just ticked, not to measure the instant itself.

Measured production results#

MetricObserved value
RMS offset~0.07 to 1.7 µs (worst case)
Drift (skew)0.007 to 0.05 ppm

These numbers aren’t a theoretical ceiling out of reach: they come from a Raspberry Pi 5 with an indoor GNSS antenna sitting on a windowsill, no shielding, no dedicated technical room.

Pitfalls to avoid (at the architecture level)#

  • Confusing precision and accuracy. A server can be very stable (low drift) while offset by a constant value, or the opposite. The following chapters show how to verify both.
  • Neglecting thermal stability. The component that clocks the system (an ordinary, uncompensated quartz oscillator) drifts with temperature, roughly 0.1 ppm/°C. A poorly ventilated enclosure can silently degrade precision without dedicated measurement (see the Thermal stabilization and From bare crystal to pseudo-TCXO chapters).
  • Neglecting security because “it’s just an NTP server”. Any service exposed on the Internet is a target. This guide builds in hardening from the start, not as an afterthought.

Verify#

One thing to verify at this stage, and it is the most important one: motivation. This guide is long, meticulous, occasionally laborious: dozens of chapters, checks at every step, repeated reboots, and a few nights spent watching an RMS offset evolve. If the urge is still intact after this page, the most critical requirement of the project is met, and none of the following chapters will test it as honestly. The next chapter lays out the metrology vocabulary (precision, accuracy, stability) that the rest of the guide relies on.


📚 Going further

  • The NTP stratum hierarchy is defined in RFC 5905 (Network Time Protocol Version 4: Protocol and Algorithms Specification, D. Mills et al., 2010), § 3.1.
  • The on-wire protocol (offset computed from 4 timestamps) and the source selection algorithm (derived from Marzullo’s algorithm, 1984, adapted by Mills for NTP) are covered in the Chrony chapter.

Sources and acknowledgments#

Beyond the RFCs cited throughout this guide, three resources served as a constant reference while writing it, and deserve explicit credit:

The Anatomy of the NTP packet chapter owes its existence directly to these three readings.