Stratum-1 NTP Server



Total cost for this project is around $130. I had a number of these items already, so the cost was considerably less.

OS and Setup

After acquiring the needed materials, you will need to setup your Raspberry Pi and install an OS. I chose Raspbian, any modern linux distro that’ll run on the Pi will do.

This guide focuses on the Pi2 however all the steps are the same for the original Pi. You will need to use a different header with the GPS HAT, and you have fewer GPIO pins to work with for the HAT, but it does not effect GPS.



One of the first steps you will need to do is to ensure that nothing else is using the UART. By default, raspbian has a console configured on ttyAMA0, you will need to disable it by removing ‘console=ttyAMA0,115200’ from /boot/cmdline.txt

apt-get install pps-tools
echo "dtoverlay=pps-gpio,gpiopin=4" >> /boot/config.txt 
echo "pps-gpio" >> /etc/modules


To test things out, we are going to install some GPS software

apt-get install gpsd gpsd-clients

Then run the following to see if GPS is working

gpsmon /dev/ttyAMA0

After letting it run for a while, you should get a 3D fix.

Modify /etc/default/gpsd and change START_DAEMON to false. We do not want gpsd starting, as it will prevent NTP from opening it directly.

Create /usr/local/bin/



        # Only output
        /bin/echo -e "\$PMTK314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0*29\r\n"

        # Output once a second
        /bin/echo -e "\$PMTK220,1000*1F\r\n"

        # Sample once a second
        /bin/echo -e "\$PMTK300,1000,0,0,0,0*1C\r\n"

        # Set baud to 115200
        /bin/echo -e "\$PMTK251,115200*1F\r\n"


# Set Port speed to 115200
/bin/stty -F $DEVICE raw 115200 cs8 clocal -cstopb
chmod +x /usr/local/bin/

Create a new file in /etc/udev/rules.d called 99-gps.rules with the following

KERNEL=="pps0",SYMLINK+="gpspps0",GROUP="dialout", MODE="0660"
KERNEL=="ttyAMA0", SYMLINK+="gps0",GROUP="dialout", MODE="0660",RUN+="/usr/local/bin/ /dev/%k"

Reboot your pi and lets ensure things load properly.

Check PPS

ppstest /dev/pps0

Check that /dev/gps0 and /dev/gpspps0 exist.

Check that GPS is outputting $GPZDA data once a second.

cat /dev/gps0

should give you something like this


You should NOT have extra line breaks between the sentences, if you do it will mess up NTP timing.

We want to use GPZDA and not GPRMC or other sentence types as they are the shortest and faster to parse for just time and thus will reduce latency. The difference isn’t much, but it’s something and since NTP will be holding the device exclusively, there is no point in getting navigational data.

In theory, you should be able to use gpsd with a shared memory segment, and rely on PPS to keep things in sync, but I haven’t tested this and I don’t need GPS navigational data for my NTP server.

Install NTP

The first thing you need to NTP with a build in NMEA driver with PPS support. To get that, you must recompile NTP with ATOM enabled.

Ensure your deb-src repos are enabled in /etc/apt/source.list

Install the necessary packages

apt-get build-dep ntp

Get the source

apt-get source ntp

Modify debian/rules and add –-enable-ATOM to the configure call.

Increase the version number in debian/changelog to be just below the next version using “~” (e.g. change 4.2.6.p5+dfsg-2 to 4.2.6.p5+dfsg-3~pps1).

Compile the package

dpkg-buildpackage -b

Install the package

dpkg -i ntp_4.2.6.p5+dfsg-7+deb8u2~pps1_armhf.deb

Configure NTP

Run the following to add NTP to dialout group so it has permissions to our devices

usermod -G dialout ntp

If DHCP is enabled, remove “ntp-servers” from the “request” line in /etc/dhcp/dhclient.conf
Remove /var/lib/ntp/ntp.conf.dhcp if present

MODE2 Calibration

Before we can configure NTP for how we will ultimately use it, we first need to use NTP to calculate the delay between GPS and PPS signals. This delay occurs because of processing of the GPS chip, the Pi, antenna wire length, etc. If we do not do this, then NTP will often times never get a PPS lock, or will mark our GPS source as a false ticker, or will simply be off a few hundred milliseconds.

First step is to change the CPU Governor from the default of ‘ondemand’ to ‘performance.

echo 'performance' > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

For calibration it’s critical or frequency changes will affect jitter. I recommend you add the above to /etc/rc.local so that’s applied on boot, but that’s up to you.

Create a calibration /etc/ntpd.conf.

server mode 88 minpoll 3 maxpoll 3 iburst prefer true
fudge refid GPS

server minpoll 3
fudge flag1 1 flag3 1 refid PPS

restrict -4 default kod notrap nomodify nopeer noquery
restrict -6 default kod notrap nomodify nopeer noquery

restrict ::1

Start NTP

ntpd -g -u root 

Check Status

# ntpq -pn

In this calibration ntp config file, we use two time sources a NMEA (20) driver and an ATOM (22) driver. We will use the differences in offset and jitter between these two to calculate the delay needed for the mode2 value in our final config file.

Where did the magic value of mode 88 come from you ask? 80 tells NTP to talk to /dev/gps0 using a baud rate of 115200. The 8 indicates to use $GPZDA and not $GPRMC or other NMEA sentences.

You should eventually get something like the below. Rerun until both sources are getting data.

remote refid st t when poll reach delay offset jitter
* .GPS. 0 l 1 8 17 0.000 5.757 22.346
o127.127.22.0 .PPS. 0 l - 8 3 0.000 414.437 0.946

If instead of an ‘o’ or ‘*’ you get some other status such as ‘x’, that’s ok for this test. It just indicates that NTP wont use it as a time source. For the sake of calibration, we don’t care we just want to know the approximate difference in offsets. The important part here is the value for ‘reach’ indicating it’s communicating and it is updating ‘offset’ for both sources.

Cheat Sheet for NTP Server Status

o = pps peer
* = sys peer
# = too distant
+ = selected
x = false ticker
- = discarded

We can calculate from the above that the difference in offsets between GPS and PPS at this time is (414.437 – 5.757) plus or minus a jitter of (22.346 + 0.946) or 408.68 +/ 23.292. This gives us a best guess mode2 value between 385.388 and 431.972. If we rerun ntpq -pn for a while, we can start to get an idea of what this value should be.

NTP will tolerate mode2 being off by as much as 400ms across samples before it throws out PPS, but GPS signal can hop around quite erratically as well so it’s best to get as close to this value as possible.

I ran mine for a few hours and logged the values to a file and picked a dozen or so samples with low jitter for both servers and came up with a value of 452.

We will also need to take into account leap seconds, adjusting our clock as necessary. Create the following shell script, and install it into “/usr/local/bin/”:

cd /etc/ntp
wget -q &> /dev/null
service ntp restart &> /dev/null

Then, create an entry in root’s crontab(5):

0 0 31 6,12 * /usr/local/bin/

Make sure it’s executable:

chmod a+x /usr/local/bin/

Modify /etc/ntp.conf to resemble below

driftfile /var/lib/ntp/ntp.drift
leapfile /etc/ntp/leap-seconds.list
#statsdir /var/log/ntpstats/
statistics loopstats peerstats clockstats
filegen loopstats file loopstats type day enable
filegen peerstats file peerstats type day enable
filegen clockstats file clockstats type day enable

server mode 88 minpoll 3 maxpoll 3 iburst prefer
fudge flag1 1 flag2 0 flag3 1 time1 0.0 time2 0. refid GPS

pool iburst

restrict -4 default kod notrap nomodify nopeer noquery
restrict -6 default kod notrap nomodify nopeer noquery

restrict ::1

Replace with the time2 value you calculated from above and then restart ntpd.

service ntp restart


ntpq -pn

After a minute or so you should get something like the following

     remote           refid      st t when poll reach   delay   offset  jitter
o127.127.20.0    .GPS.            0 l    1    8    3    0.000    0.002   0.001
+    2 u    2   64    1   40.306    0.943   0.055   2 u    1   64    1   81.439    1.836   0.076  2 u    2   64    1   40.201    0.597   0.026   2 u    1   64    1   46.196   -2.844   5.787

If instead of a ‘o’ you have a ‘*’ next to GPS source, you need to give ntp more time to get PPS lock OR you didn’t recompile NTP with PPS support. Mine usually gets a lock within a minute, however on rare occasion it never gets lock. I can only assume this is because of timings and they happen to be far enough off that lock never happens. Stopping ntp and starting it again results in a PPS lock within a minute.

# ntpq -c rl
associd=0 status=041d leap_none, sync_uhf_radio, 1 event, kern,
version="ntpd 4.2.6p5@1.2349-o Mon Nov  9 22:24:25 UTC 2015 (1)",
processor="armv7l", system="Linux/4.1.12-v7+", leap=00, stratum=1,
precision=-20, rootdelay=0.000, rootdisp=0.168, refid=GPS,
reftime=d9ef5275.e7aa703a  Thu, Nov 12 2015 10:01:25.904,
clock=d9ef527b.73e87700  Thu, Nov 12 2015 10:01:31.452, peer=39586, tc=3,
mintc=3, offset=0.001, frequency=-5.331, sys_jitter=0.002,
clk_jitter=0.000, clk_wander=0.001

We are a stratum 1 server, serving a stratum 0 device with a precision of 953.67 nanoseconds (2^-20 seconds) and a jitter of 2 microseconds!


Now that we think we have everything setup correctly, start a capture

while [ 1 ]; do ntpq -pn | head -n 3 | tail -n 1; sleep 1; done >> gps.log

After 24 hours or so, run the following to generate a CSV file. Use this to graph your results using excel, google docs or ploty.

cut -c 71- gps.log | sed 's/^ \+//' > data.csv

Compare them to this graph