Build your own time server with a GPS receiver.

28 November 2018

If you've had your ear to the ground lately, you might have heard that the NIST timekeeping radio station used by devices all over the world as a time reference for Coordinated Universal Time as well as some experiments in signal propagation and geophysical event notices might be on the chopping block in 2019, leaving the HF bands quieter and, let's face it, we can't have nice things.  Clocks that rely on this time source signal won't have any way to stay in sync and the inevitable drift due to the imperfections in everything will cause fractions of second to be lost and a fresh outbreak of kinetic pattern baldness.  The ultimate effects of this latest bit of clueless petulance on the part of Donald Trump remain to be seen, but it seems likely that this isn't a sexy enough problem to catch brainshare like Y2k did.  If you work extensively with computers chances are you're not that worried because your machines use NTP - the Network Time Protocol - to synch their internal clocks with a known time reference server on the Net someplace.  Something to consider, however, is whether or not your upstream tier-one and tier-two time sources are actually using the NIST WWV time singnals as their reference signals.  There is, however, a nifty way around this: Build your own NTP server that uses a reference time source that can't be shut off as a source, the Global Positioning System.

First, I'll show you how to build your own GPS time server, and then I'll explain why it works.

As my proof of concept when writing this article I used stuff sitting around on my workbench, a spare Raspberry Pi 3 and a USB GPS receiver a little bigger than a flash drive.  If I'd purchased them for this project I'd have paid around $56.95us for the whole kit, but as long as you have a Linux box of some kind (possibly even a wireless router with a USB jack running OpenWRT) you'd really only have to buy a GPS receiver.  The following instructions are written from the Raspbian/Debian/Ubuntu perspective because I used a RasPi as my proof-of-concept but implementing the process on other distros should be fairly straight forward.

First, if you're using a RasPi for this project, you'll have to make a minor configuration change because systemd will conflict with any USB serial devices you plug in because it tries to run a serial terminal on the same serial port.  Edit the file /boot/cmdline.txt with your favorite text editor and delete the string "console=serial0,115200".  The file should look subsequently look something like this.  Reboot your RasPi and log back in.

Now you need to install a couple of software packages from the Raspbian project.  Three of them are component packages of NTP, the other two are for GPS support.

pi@raspberrypi:~ $ sudo apt-get install -y ntp ntpdate ntpstat gpsd gps-clients

gpsd needs a minor configuration change; edit /etc/default/gpsd and change GPSD_OPTIONS="" to read GPSD_OPTIONS="-n".  This is so that gpsd will start up and wait until it can see a GPS unit on the system rather than start up, sometimes fail to find the GPS and die silently (thank you, systemd).  The /etc/default/gpsd file will now look something like this:

pi@raspberrypi:~ $ cat /etc/default/gpsd 
# Default settings for the gpsd init script and the hotplug wrapper.

# Start the gpsd daemon automatically at boot time

# Use USB hotplugging to add new USB devices automatically to the daemon

# Devices gpsd should collect to at boot time.
# They need to be read/writeable, either by user gpsd or the group dialout.

# Other options you want to pass to gpsd

Enable and then start gpsd.  If your GPS isn't already plugged in, plug it in.

pi@raspberrypi:~ $ sudo systemctl enable gpsd
pi@raspberrypi:~ $ sudo systemctl start gpsd

Now to test the GPS receiver to make sure it works.  One of the packages you installed earlier provides the cgps utility, which connects to gpsd and prints the output from the GPS unit.  If your GPS is working and it's gotten a lock from at least one GPS satellite you should see something like this in your terminal, updated in realtime.  Note the value of the time field on your screen: 2018-11-24-T22:07:17.17000Z. This is an ISO 8601 timestamp: November 24, 2018, at 10:07:17pm Greenwich Mean Time (UTC and GMT aren't actually the same but, colloquially speaking tend to be used interchangeably).  This is what your time server is going to use as its canonical upstream time.  Hit 'q' to quit cgps.

Next, ntpd needs to be configured to use the GPS receiver plugged into the server as its time source.  Edit the /etc/ntp.conf file and add the following text to the end.

# GPS Serial data reference
server minpoll 4 maxpoll 4
fudge time1 0.0 refid GPS

# GPS PPS reference
server minpoll 4 maxpoll 4 prefer
fudge refid PPS

Those new configuration directives mean this:

  • server - IP address of the upstream time source to contact.  The IP address is a special pseudo-address which tells ntpd "Access the shared memory segment agreed upon by the developers of both ntpd and gpsd for the first local time source and read the current time from there."  This is handwavey magick done so we don't have to fiddle around with it too much.  The IP address means the same thing for the second local time source.
  • minpoll - The minimum number of time intervals (periods of 2n seconds) in which there must be an answer from gpsd.
  • maxpoll -The maximum number of time intervals (periods of 2n seconds) in which there must be an answer from gpsd.
  • fudge - Additional configuration information for the specified upstream time server.  No, I don't know why they called it this.
  • time1 - Add a constant number of seconds from whatever value you get from this server as a known adjustment factor.  In this case, 0.0, or nothing.
  • refid - An arbitrary string naming the time server.  The names we use here are GPS and PPS.
  • prefer - Prefer this server over others in the file.

Restart ntpd.

pi@raspberrypi:~ $ sudo systemctl restart ntp

Verify that ntpd is pulling its time reference from the GPS with the command ntpq -p.  If it's working you should see output something like this:

root@raspberrypi:/etc# ntpq -p
     remote           refid      st t when poll reach   delay   offset  jitter
 0.debian.pool.n .POOL.          16 p    -   64    0    0.000    0.000   0.001
 1.debian.pool.n .POOL.          16 p    -   64    0    0.000    0.000   0.001
 2.debian.pool.n .POOL.          16 p    -   64    0    0.000    0.000   0.001
 3.debian.pool.n .POOL.          16 p    -   64    0    0.000    0.000   0.001
 SHM(0)          .GPS.            0 l    -   16    0    0.000    0.000   0.001
 SHM(1)          .PPS.            0 l    -   16    0    0.000    0.000   0.001  .PPS.            1 u    -   64    1   61.429   43.026   0.001   2 u    -   64    1   97.462   46.510   0.001

You will see the two new lines for SHM(0) and SHM(1), the former for GPS, the latter for PPS.  These are the GPS feeding timing data to ntpd in addition to using the public Debian NTP server pool.  Success!


I didn't try using OpenNTPd for this project but supposedly you can do so with the nmea driver.

If you don't see any usable output from cgps it could be that your GPS receiver can't get a clear enough signal to get a lock.  You may need to use a GPS with an external antenna, mount said antenna someplace where there's less metal or reinforced concrete, or move your entire server someplace with a better view of the sky (Raspberry Pis are perfectly sized for this).

Now... why does this work?

Without getting brain-meltingly technical about it, the heart of every GPS navigation satellite is an extremely precise atomic clock.  The satellite continually transmits a signal earthward which carries messages that consist of, among other things, the time the message was transmitted (as far as the satellite's internal clock is concerned), the satellite's position in the sky, and a continually updated pseudorandom code (not a cryptographic key, but a sequence that can be predicted as long as you know a couple of things about it) generated by the satellite.  Every GPS receiver, when running also continually generates and update that pseudorandom code.  Every GPS satellite is in medium earth orbit, at an altitude of roughly 12,540 miles, which is a distance at which there is a noticeable lag between when a message is sent and when it is received on the ground.  The receiver uses the times in the messages received from multiple GPS satellites and the pseudorandom codes to calculate not only its current coordiantes in 3D space, but the current time.  That current time is what we're using to synch our server (but we can use the coordinates for other things if we want).