Pi4 Stratum 1 Time Server

From Gentoo Wiki
Jump to:navigation Jump to:search

This article covers setting up a Raspberry Pi 4 as a Stratum 1 Time Server.

Any Pi will do for a Stratum 1 NTP server. A Pi4, that does not use USB for Ethernet, is recommended for minimal jitter. It also provides two USB3 ports that can be used for gigabit Ethernet so that routing can be added.

There is a lot of documentation on Pi stratum 1 NTP servers around the internet. Most of it applies to earlier models of the Pi, which have completely different I/O arrangements.

This document is only for a Pi4


The price of electricity in the UK has gone up by a factor of four in the last six months (August, 2022).

I have been running an original Pi B with 256MB RAM (one of the first 10,000 made) as a stratum 1 NTP server, using GPS as a source of atomic time, to get to stratum 1. Just for my internal network. It was just a load and go Debian install, that I always intended to upgrade to Gentoo.

I have also been running a HP Gen 7 Microserver, hosting various KVMs. I'm just left with my router KVM and original Pi NTP server.

By moving the Pi NTP server to a Pi4, there will be enough spare I/O bandwidth for the router too. The net result is that the Microserver can be turned off, saving about 60W on my base load, or about 1.5kW/h per day, or £22.50/month.


Hardware Requirements

GPS HAT Fitted to a Pi 4

The following hardware is required to follow on with this article:

  • A Pi4 -- this article was based on a repurposed 4GB Pi 4.
  • A GPS Time HAT with the PPS on GPIO 18. Do fit the battery to keep the clock alive.

That it's a GPS Time HAT with the PPS on GPIO 18 matters.

The image shows a GPS HAT, designed for a Pi 1, fitted to a Pi 4 - hence the additional GPI0 Pins.

The Pi 4 has Wifi but the aluminium case illustrated is an excellent Faraday cage, so the Wifi cannot be used

Software Requirements

The following packages are required:

A Pi should already have net-misc/ntp, as its missing an RTC but it will need to be configured as a server for the local network and to use localhost as a source of time.

sci-geosciences/gpsd takes care of the gps side of things.

Installation Site

The GPS antenna needs a clear view of sky towards the equator from it's position. It may not work indoors, even behind windows that are coated to retain/reflect heat as these coatings can reflect/block the GPS radio signal.

How It Works

GPS works by solving four simultaneous equations, to derive Latitude, Longitude, Altitude and Time from at least four satellites. Modules that only care about time assume that they are at a fixed location and average out Latitude, Longitude and Altitude changes, which produces a better quality of time.

There are actually two solutions to the simultaneous equations, given only four satellites. One close to the earth, one thousands of miles away in space. The latter is discarded.

PPS on GPIO 18 is the default in the Pi pps-gpio overlay. If the GPS HAT uses a different GPIO pin, a parameter must be passed to dtoverlay=pps-gpio in /boot/config.txt.

The PPS produces an interrupt, which is the boundary between seconds. It says nothing about which seconds the boundary is between.

Time is read from the GPS module over the serial port. That will be /dev/ttyS0.

Setting Up

Carry out a basic install. Follow the Pi4 Install guide. Use the default/linux/arm64/23.0 profile as the NTP server will be headless.

Using the mainline kernel should work but old habits die hard.

As the mini-UART will be used, Wifi and Bluetooth can work, but are not included here. Bluetooth is connected to the PL011 main UART. Wifi uses the second SDIO interface.


At the time of writing, parse-clocks is masked on net-misc/ntp for arm64. The mask is over 10 years old and it appears to work for some. Meanwhile, add an entry to /etc/portage/profile/package.use.mask/ntp that reads net-misc/ntp -parse-clocks.



  • After further testing, fix the ::gentoo repo.

Build net-misc/ntp with USE="caps ipv6 parse-clocks readline ssl threads".

parse-clocks adds PPS support, which is fairly essential.


sci-geosciences/gpsd needs USE="cxx ipv6 ncurses ntp python shm sockets" and support for the protocol used by the GPS module.

All are built by default. GPSD_PROTOCOLS="+aivdm +ashtech +earthmate +evermore +fury +fv18 +garmin +garmintxt +geostar +gpsclock +greis +isync +itrax +mtk3301 +navcom +nmea0183 +nmea2000 +ntrip +oceanserver +oncore +passthrough +rtcm104v2 +rtcm104v3 +sirf +skytraq +superstar2 +tnt +tripmate +tsip +ublox"

Set GPSD_PROTOCOLS in make.conf according to taste.

gpsmon reports the protocol is use. Such as:

root #gpsmon
tcp://localhost:2947          NMEA0183>

Kernel Options

make bcm2711_defconfig should just work. It has PPS_GPIO and 8250_SERIAL support available. The former is for our PPS interrupt and the latter is for the mini-UART.

It's full of things not needed, so feel free to slim it down.

A Pi 4 takes about an hour to build a bcm2711_defconfig kernel for itself, so cross compiling is not the essential that it once was.

Putting the Pieces Together.

Now all the bits are ready, it's time to set up the configuration files and start services.


In addition to everything else, /boot/config.txt requires:

FILE /boot/config.txt
# GPS Time
# For GPS 1 PPS

Adding a non default GPIO pin here is left as an exercise for the reader.

This loads the pps-gpio overlay, which sets up GPIO 18 as an input and attaches it to an IRQ. /proc/interrupts will include something like:

root #cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3       
 49:      90274          0          0          0  pinctrl-bcm2835  18 Edge      pps@12.-1


First and foremost, /boot/cmdline.txt must be a single line.

It needs to include 8250.nr_uarts=1 to enable the mini-UART.

The default 8250.nr_uarts=0 disables the mini-UART so no serial data will be shown.


The non comment lines in /etc/conf.d/gpsd need to read:

FILE /etc/conf.d/gpsd

The only change should be to add /dev/ttyS0.

Non Pi4 documents will cite /dev/ttyAMA0 as the DEVICE.


/etc/ntp.conf needs some changes.

The first change allows all of to get time from the NTP server. If the LAN uses a different private network range, change the restrict statement:

FILE /etc/ntp.conf
# Let all the LAN get time
restrict mask nomodify nopeer notrap

Tell ntpd to use the PPS:

FILE /etc/ntp.conf
# pps-gpio on /dev/pps0
server minpoll 4 maxpoll 4
fudge refid PPS
fudge flag3 1  # enable kernel PLL/FLL clock discipline

And the Shared Memory Clock:

FILE /etc/ntp.conf
# gpsd shared memory clock
server minpoll 4 maxpoll 4 prefer  # PPS requires at least one preferred peer
fudge refid GPS
fudge time1 +0.130  # coarse processing delay offset

Find a local NTP pool to use, rather then the Gentoo pools. For example, for a UK pool:

FILE /etc/ntp.conf
# a UK based pool
pool uk.pool.ntp.org minpoll 10 iburst


Add both ntpd and gpsd to the default runlevel, then reboot to test.

Powering Up The First Time

Acquiring the first satellite can take from minutes to hours. However, all satellites transmit the ephemeris, which tells the receiver where all the other satellites are. Once the receiver has the time and ephemeris, the battery makes resuming operation after a power loss fairly quick.

There are GPS receivers that have a PPS LED, which flashes once satellites have been acquired. Interestingly, in one test, the receiver will not acquire the first satellite behind thermal glass but does maintain lock once it has the ephemeris.

Testing the NTP Server

Look at the output of uname -a and check that the date and time correspond to the date and time of the kernel build.

In the example, the position, accurate to a few meters, has been redacted:

root #gpsmon
tcp://localhost:2947          NMEA0183>
│Time: 2022-08-28T14:30:40.000Z   Lat: xx xx.xxxxxx' y   Lon: zzz zz.zzzzzz' a │
└───────────────────────────────── Cooked TPV ─────────────────────────────────┘
│ GLGSV GNGLL GNRMC GNVTG GNGGA GNGSA GPGSV                                    │
└───────────────────────────────── Sentences ──────────────────────────────────┘
│ SVID  PRN  Az El SN HU│Time:     143040.00	  │Time:      143040.00        │
│GP  2    2 315 22 25  Y│Latitude:   xxxx.xxxxx y │Latitude:  xxxx.xxxxx       │
│GP  3    3 103 11 18  Y│Longitude: zzzzz.zzzzz a │Longitude: zzzzz.zzzzz      │
│GP  4    4  66 45 21  Y│Speed:    0.428          │Altitude:  55.3             │
│GP  6    6 226 64 18  Y│Course:                  │Quality:   1   Sats: 12     │
│GP  7    7 159 23 30  Y│Status:   A        FAA:A │HDOP:      0.82             │
│GP  9    9 125 78 29  Y│MagVar:                  │Geoid:     bb.b             │
│GP 11   11 292 49 27  Y└───────── RMC ───────────└─────────── GGA ────────────┘
│GP 19   19 219  8 27  Y┌─────────────────────────┌────────────────────────────┐
│GP 20   20 287 26 21  Y│Mode: A3 Sats: 2 3 4 6 + │UTC:           RMS:         │
│GL  3   67  67 23 17  Y│DOP H=0.82 V=1.06 P=1.34 │MAJ:           MIN:         │
│GL  4   68  38 73 24  Y│TOFF:  0.130244530	  │ORI:           LAT:         │
│GL  5   69 263 46 24  Y│PPS: N/A                 │LON:           ALT:         │
└───v──── GSV ──────────└────── GSA + PPS ────────└─────────── GST ────────────┘

As all the satellites are all located above the receiver, altitude is very poor.

Notice the sync_pps and stratum=1 below:

root #ntpq -c rv
associd=0 status=0118 leap_none, sync_pps, 1 event, no_sys_peer,
version="ntpd 4.2.8p15@1.3728-o Sun Aug 28 16:43:23 UTC 2022 (1)",
processor="aarch64", system="Linux/5.15.61-v8+", leap=00, stratum=1,
precision=-22, rootdelay=0.000, rootdisp=1.105, refid=PPS,
reftime=e6b62c86.af3504fd  Sun, Aug 28 2022 18:03:50.684,
clock=e6b62c8d.b4dd7b5b  Sun, Aug 28 2022 18:03:57.706, peer=26483, tc=4,
mintc=3, offset=-0.016612, frequency=-9.991, sys_jitter=0.002136,
clk_jitter=0.006, clk_wander=0.006

The microsecond values of jitter and wander are encouraging too:

root #ntpq -p
     remote           refid      st t when poll reach   delay   offset  jitter
*PPS(0)          .PPS.            0 l   14   16  377    0.000   -0.170   0.042
xSHM(0)          .GPS.            0 l   13   16  377    0.000  +22.963   0.801
+ (d     3 u   21   64  377   25.015   +0.157   0.489
+time.shf.uk.as4      3 u   11   64  377   19.868   -0.080   0.259
+ntp-cov-1.lewis     2 u   29   64  377   24.786   -0.266   0.255

Offset and jitter are in milliseconds. Values of a few microseconds can be expected given a few days operation.

Client Configuration

Its all very well having a local working stratum 1 NTP server on your network. Tell your clients all about it or it will not be used.

The default gentoo /etc/ntp.conf needs a one line addition

FILE /etc/ntp.conf
# Pi4 Router/Time Server
server prefer iburst

This allows the four gentoo.pool.ntp.org entries to remain as fallbacks.

Restart the ntpd service.

Client Testing

user $$ ntpq -c rv
associd=0 status=0615 leap_none, sync_ntp, 1 event, clock_sync,
version="ntpd 4.2.8p15@1.3728-o Fri Feb 17 18:43:26 UTC 2023 (1)",
processor="x86_64", system="Linux/6.2.1-gentoo", leap=00, stratum=2,
precision=-24, rootdelay=0.137, rootdisp=71.062, refid=,
reftime=e7e92c1d.8a69a49c  Tue, Apr 18 2023 15:47:57.540,
clock=e7e92c95.bff11931  Tue, Apr 18 2023 15:49:57.749, peer=12183, tc=7,
mintc=3, offset=+0.156207, frequency=+1.166, sys_jitter=0.120063,
clk_jitter=0.187, clk_wander=0.013

Notice the refid=, which is the IP address of the server.


  1. Add IPv6 configuration.

See also