Andras Dosztal
Andras Dosztal
Network architect
Apr 23, 2022 4 min read

I built a Stratum-1 NTP server at home

thumbnail for this post

My friend Jacques surprised me with a GPS module and I made a good use of it. The components I used are:

  • a Raspberry PI 4 (you can definitely use a v3, and probably a v2 as well),
  • a cheap ass GPS module (less than €5) from Ali,
  • and an optional GPS antenna, stiched to my skylight window, to get a better reception.

I was following the awesome post of Austin’s Nerdy Things; however there were some differences (maybe coming from the newer Raspbian version?) that I’m going to explain in this post.

Step 0

I did exactly as described except there’s an extra step to do, which comes useful later in step 4.5: disabling the factory-default serial console that would block receiving the GPS data. For this you just need to run raspi-config:

sudo raspi-config

Select Interface Options, then Serial Port. The first question asks if you want a serial console, answer No to this. Then it asks if you want to enable serial port at all, answer Yes to this.

Step 1

I did everything the same way as described in the original post.

Step 2

Again the same as in the original post, except 5V pin is called VCC instead of VIN:

GPS module pins

Step 3

A slight difference in the loaded modules, however this doesn’t affect the configurations you need to apply before and after this step:

adosztal@raspi4:~ $ lsmod | grep pps
pps_ldisc              16384  2
pps_gpio               16384  2

Running sudo ppstest /dev/pps0 gave similar results to Austin’s post:

adosztal@raspi4:~ $ sudo ppstest /dev/pps0
trying PPS source "/dev/pps0"
found PPS source "/dev/pps0"
ok, found 1 source(s), now start fetching data...
source 0 - assert 1650867765.999998081, sequence: 226856 - clear  0.000000000, sequence: 0
source 0 - assert 1650867766.999995683, sequence: 226857 - clear  0.000000000, sequence: 0
source 0 - assert 1650867768.000001360, sequence: 226858 - clear  0.000000000, sequence: 0
source 0 - assert 1650867768.999998352, sequence: 226859 - clear  0.000000000, sequence: 0

Step 4

In addition to setting the gpsd daemon’s devices and options to the values described in the post, enabling the service should be done using the proper systemd commands:

sudo systemctl enable gpsd.socket gpsd.socket
sudo systemctl enable gpsd.socket gpsd.service
sudo systemctl start gpsd.socket gpsd.socket
sudo systemctl start gpsd.socket gpsd.service

Note: There are some articles on the internet that suggest disabling gpsd.socket but those run the daemon in an ad-hoc fashion from the CLI. To get a reliable service, you need to enable these daemons.

Step 4.5

This is where I spent some time troubleshooting before I realized the serial console is locking my port. As I mentioned in step 3, running sudo ppstest /dev/pps0 gave proper results but when I ran gpsmon, I just saw some JSON output without any valuable information. First I was afraid it was an unresolvable problem with the hardware until I finally figured out the root cause was the locked ttyS0 port; fixing that resolved the issue and gpsmon shows a bunch of satellites:

tcp://localhost:2947          NMEA0183>
┌──────────────────────────────────────────────────────────────────────────────┐or":14}
│Time: 2022-04-25T06:40:01.000Z   Lat: 47 11.584590' N   Lon:  18 27.262610' E │r":"u-blox","subtype":"SW 1.00
└───────────────────────────────── Cooked TPV ─────────────────────────────────┘2022-04-25T06:38:57.386Z","fla
┌──────────────────────────────────────────────────────────────────────────────┐02},{"class":"DEVICE","path":"
│ GPGSV GPGLL GPRMC GPVTG GPGGA GPGSA                                          │
└───────────────────────────────── Sentences ──────────────────────────────────┘false,"timing":false,"split24"
┌───────────────────────┌─────────────────────────┌────────────────────────────┐
│ SVID  PRN  Az El SN HU│Time:     064001.00      │Time:      064001.00        │
│GP 12   12 249 49 12  Y│Latitude:   4711.58459 N │Latitude:  4711.58459       │
│GP 17   17  56 38 16  Y│Longitude: 01827.26261 E │Longitude: 01827.26261      │
│GP 19   19  82 51 21  Y│Speed:    0.723          │Altitude:  162.6            │
│GP 24   24 300 76 23  Y│Course:   169.04         │Quality:   1   Sats: 04     │
│GP  1    1  17  3 16  N│Status:   A        FAA:A │HDOP:      3.77             │
│GP  6    6 112  8 16  N│MagVar:                  │Geoid:     39.7             │
│GP 10   10 294 11  3  N└───────── RMC ───────────└─────────── GGA ────────────┘
│GP 13   13 165 20  7  N┌─────────────────────────┌────────────────────────────┐
│GP 14   14  64  6  0  N│Mode: A3 Sats: 12 17 19 +│UTC:           RMS:         │
│GP 15   15 200 36  5  N│DOP H=3.77 V=4.49 P=5.86 │MAJ:           MIN:         │
│GP 23   23 263  7  0  N│TOFF:  0.135858576       │ORI:           LAT:         │
│GP 25   25 249  8  0  N│PPS: -0.000004742        │LON:           ALT:         │
└───v──── GSV ──────────└────── GSA + PPS ────────└─────────── GST ────────────┘
(66) $GPGSV,4,3,14,19,51,082,23,23,07,263,,24,76,300,25,25,08,249,*7E
(42) $GPGSV,4,4,14,28,29,060,25,32,05,326,*7F
(52) $GPGLL,4711.58523,N,01827.26245,E,064000.00,A,A*6A
------------------- PPS offset: -0.000004742 ------
(68) $GPRMC,064001.00,A,4711.58459,N,01827.26261,E,0.723,,250422,,,A*7D
(35) $GPVTG,,T,,M,0.723,N,1.339,K,A*2D
(75) $GPGGA,064001.00,4711.58459,N,01827.26261,E,1,04,3.77,162.6,M,39.7,M,,*5D
(50) $GPGSA,A,3,17,12,19,24,,,,,,,,,5.86,3.77,4.49*08
(70) $GPGSV,4,1,14,01,03,017,16,06,08,112,16,10,11,294,03,12,49,249,12*7E
(68) $GPGSV,4,2,14,13,20,165,07,14,06,064,,15,36,200,05,17,38,056,16*71
(66) $GPGSV,4,3,14,19,51,082,21,23,07,263,,24,76,300,23,25,08,249,*7A
(42) $GPGSV,4,4,14,28,29,060,24,32,05,326,*7E
(52) $GPGLL,4711.58459,N,01827.26261,E,064001.00,A,A*61

Final results

Once I fixed the issues, I saw my time server getting very reliable data (<200 ns offset):

adosztal@raspi4:~ $ chronyc sources
MS Name/IP address         Stratum Poll Reach LastRx Last sample               
===============================================================================
#x NMEA                          0   4   377    13    -75ms[  -75ms] +/- 1198us
#* PPS                           0   4   377    11    +85ns[ +156ns] +/-  215ns
^- vps.poljakovi.cz              2  10   377   150  -2270us[-2269us] +/-   44ms
^- herbrand.noumicek.cz          2  10   377   227  -2368us[-2366us] +/-   39ms
^- host189-248-2-81.serverd>     2  10   377   272  -3426us[-3424us] +/-   15ms
^- ntp3.kashra-server.com        2  10   377   103  -1995us[-1995us] +/-   33ms

I still kept some NTP servers for two reasons:

  1. For failover if something breaks.
  2. For some reason PPS was marked as “in error” if I didn’t have any other sources.