How to Setup a GPS PPS NTP Time server on Raspberry Pi

The South Florida Amateur Astronomers Association is currently working on a project to coordinate multiple Radio Telescope collection points.   This would require very precise time stamps on recorded data, much more accurate than what public NTP server pools could deliver.     So we successfully built as close to a Stratum-1 time server as possible as cheaply as possible.

So we used a Raspberry Pi3b with the Adafruit GPS Hat and were able to setup a clock source based on kernel mode PPS pulses to get 1 microsecond of jitter.   That’s as low as the NTP service can register and as good as we can get without spending a LOT of money.

So for about $90 in parts, we were able to build a system with the needed time precision for this project to work.

Building the Pi

So to begin building this time source, I used the following:

  • Raspberry Pi 3b Board or Kit (MainBoard, power adapter, heatsinks, etc…)
  • Tall Pi Case (extra room to hold the GPS hat)
  • 16 GB Micro sd Card
  • AdaFruit GPS Hat (Model FBA_2324)
  • GPS Antenna 28 dB Gain
  • RF connecter Pigtail (SMA Female to to Ufl/ipx)  to connect the GPS antenna to the GPSHat
  • You need some Spacers/Standoffs to mount the GPS Hat properly.


Solder the GPS Hat to the plastic 24 pin adapter that came with the GPS Hat.   Then mount the Pi with GPS hat into your case using the Spacers/Standoffs as needed to help support the GPS Hat in place.  Attach the Pigtail to the GPS Hat and then to the GPS antenna.

Pi with GPS Hat

Pi with GPS Hat without pigtail installed


Install the OS

Once its all assembled, you need to install the OS.    I used the latest Raspberry Stretch with Desktop Image.    With a Windows workstation I used Etcher to burn the Raspberry Image onto the Micro SD Card.   Plug in your SD card to the Pi, connect Monitor, Keyboard, mouse, network cable, and power it up.

The Pi will boot into the default Desktop.    Default username=pi with password=raspberry.

We want to enable SSH access to the Pi by using the raspi config. Open a Terminal window in the Pi desktop and use:

sudo raspi-config
Select Interfacing Options
Select P2 SSH
Enable it.

Before leaving the window, use this to get your current IP address:

$ ifconfig | grep inet
        inet  netmask  broadcast
        inet  netmask is the IP to use as the SSH host’s IP. is your loopback IP, you can ignore this one for now.

So, with the Pi setup and on the network, we can SSH into the Pi and begin the config.
If you are on Windows, Putty is the tool you need to ssh into the host. You just need the IP, ID, and password. Mac/Linux users can already SSH from command line.


Configuring the PI to use the GPS Hat

NOTE: all the commands in the code boxes can be combined to work in a setup script. They can be copied/pasted directly into the ssh session with no user intervention needed.

Disable ipv6, this will take effect on next reboot

sed -i.bck '$s/$/ ipv6.disable=1/' /boot/cmdline.txt

Change hostname on the Pi. I use radiopi as the new name.

sudo hostnamectl set-hostname radiopi

Add new hostname to local hosts file (just to avoid error messages)

sudo sed -i 's/\tlocalhost/\tlocalhost radiopi/g' /etc/hosts

Run apt update and upgrade

sudo sh -c "apt-get update ; sudo DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold""

Install additional packages

sudo sh -c " apt-get install usbmount eject gpsd gpsd-clients python-gps pps-tools -y"

Disabling the serial tty. Because the GPS hat is going to use the serial pins on the Pi, we need to disable the Serial tty otherwise there will be a conflict.

sudo sed -i 's/console=serial,115200//g' /boot/cmdline.txt

Note that if you are using a PI3B+ then the bluetooth module may be cause an error with this config. In the comments, user Bert found the workaround. Please see his answer in the comments if you have a PI3B+ (Thanks Bert!!)

GPSD is the program used to receive the GPS data. By default, it looks for a USB based GPS module. Since ours is not USB, we disable this to speed things up.
In addition, we want to use ttyAMA0 to deliver the GPS data. Setting GPSD Options to -n ensure the GPS Daemon continues to run even if no application is using it.
Change the gpsd startup to NOT use the usb auto and to not wait for client before getting GPD fix

sed -i 's/USBAUTO="true"/USBAUTO="false"/g' /etc/default/gpsd
sed -i 's:DEVICES="":DEVICES="/dev/ttyAMA0 /dev/pps0":g' /etc/default/gpsd
sed -i 's:GPSD_OPTIONS="":GPSD_OPTIONS="-n":g' /etc/default/gpsd

Enable the GPSD daemon in systemctl so that it starts with the Pi

systemctl enable gpsd

Create symbolic links in udev rules for GPS and PPS devices so the Network Time Protocol (NTP) daemon software application can make references to them using expected names (requires reboot).

echo KERNEL==\"ttyAMA0\", SUBSYSTEM==\"tty\", DRIVER==\"\", OWNER==\"root\", GROUP==\"tty\", MODE==\"0777\", SYMLINK+=\"gps0\" >> /etc/udev/rules.d/09-pps.rules

Add pps-gpio to boot/config. The AdaFruit GPSHat that I have uses gpio4. Other devices may use different gpio pins.

echo dtoverlay=pps-gpio,gpiopin=4 >> /boot/config.txt 

Include gpio in startup modules

echo pps-gpio >> /etc/modules

If DHCP client hooks exist, it can set NTP from DHCP and we don’t want that to happen since we want to use our custom configs. Remove ntp DHCP hook (so that it doesn’t override ntp settings)

if [ -f  "/etc/dhcp/dhclient-exit-hooks.d/ntp" ]; then mv /etc/dhcp/dhclient-exit-hooks.d/ntp /etc/dhcp/dhclient-exit-hooks.d/xxxntp-orig; else echo "NTP exit hook does not exist"; fi

When the Pi starts up, GPSD seems to need a ‘kick’ to start sending data. Even though the daemon starts automatically, it still seems to need this to start the flow of data.
gpsd seems to need a kickstart to get going. Adding this to rc.local sends 1 nmea packet through the gps client and exits. gpsd will continue to work

sed -i '$ s/exit 0/gpspipe -r -n 1 \&/g' /etc/rc.local ; echo exit 0 >> /etc/rc.local

At this point, we should have the GPSD software installed and can run a few tests to make sure everything is working.
The GPS Hat has an LED that will blink rapidly while a GPS fix is being taken. It will then blink red once every 15 seconds indicating that it has a GPS fix.

You can check that the GPSHat is sending data over the tty port:

cat /dev/ttyAMA0

GPS data should be seen with

cgps -s

PPS port can be checked with:

ppstest /dev/pps0

Configuring NTP to use the PPS as Time Source

At this point, since we confirmed that we are getting data off the GPS Hat in into our Serial ttyAMA0 port, we can start the NTP install.
Install ntp

apt-get install ntp -y

Add following to ntp.conf to include GPS and PPS Fudging

cat << EOF >> /etc/ntp.conf
#Note that this config works without an internet source.
#PPS Kernel mode
server minpoll 4 maxpoll 4 true
fudge flag3 1 refid PPS

# GPS Serial data Reference
server minpoll 4 maxpoll 4 iburst prefer
fudge flag1 1 time1 0.500 refid GPS stratum 1

# Shared Memory 2 source
server minpoll 4 maxpoll 4
fudge flag1 1 refid SHM2

# Fix False tickers
tos mindist 0.5

Comment out the ntp server lines for pool 0, 1, 2, 3. We only want data from the PPS pulses as a time source so we can automatically remove the other pool hosts.

sed -i 's/pool 0./# pool 0./g' /etc/ntp.conf
sed -i 's/pool 1./# pool 1./g' /etc/ntp.conf
sed -i 's/pool 2./# pool 2./g' /etc/ntp.conf
sed -i 's/pool 3./# pool 3./g' /etc/ntp.conf

Comment out the following lines in ntp.conf, we dont need it.

sed -i 's/restrict ::1/# restrict ::1/g' /etc/ntp.conf
sed -i 's/restrict -6/# restrict -6/g' /etc/ntp.conf

Verify GPS on console and the NTP expected link for the PPS kernel mode line device:

TESTGPS=$(ls /dev/gps*); if [ "$TESTGPS" == "/dev/gps0" ]; then echo 'GPS Mapping to Console is present'; else echo 'GPS NOT mapped to console port'; fi
TESTPPS=$(ls /dev/pps*); if [ "$TESTPPS" == "/dev/pps0" ]; then echo 'NTP PPS kern mode interrupt line device present'; else echo 'NTP PPS Device NOT present'; fi

NOTE: I have a copy of my working ntp.conf at the bottom of the article.

With NTP configured, I remove the NTP application but leave the configs.
I download the latest ntp Application and do a custom compile because we want to enable the
PPS and ATOM clock options so that we can read the PPS signals properly.

# Compile NTP with PPS kernel input
sudo service ntp stop
sudo apt-get remove ntp
sudo apt-get update
sudo apt-get install libcap-dev
# Get the latest stable
tar -xvf ntp-4.2.8p10.tar.gz
cd ntp-4.2.8p10
# config/build it
./configure --prefix=/usr --enable-all-clocks --enable-parse-clocks \
--enable-SHM --enable-debugging --sysconfdir=/var/lib/ntp --with-sntp=no \
--with-lineeditlibs=edit --without-ntpsnmpd --disable-local-libopts \
--disable-dependency-tracking --enable-linuxcaps --enable-pps --enable-ATOM && make && make install
sudo make install
sudo service ntp start

NTP conf looks like this:

driftfile /var/lib/ntp/ntp.drift
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
restrict -4 default kod notrap nomodify nopeer noquery limited
restrict source notrap nomodify noquery

#Note that this config works without an internet source.
#PPS Kernel mode
server minpoll 4 maxpoll 4 true
fudge flag3 1 refid PPS

# GPS Serial data Reference
server minpoll 4 maxpoll 4 iburst prefer
fudge flag1 1 time1 0.500 refid GPS stratum 1

# Shared Memory 2 source
server minpoll 4 maxpoll 4
fudge flag1 1 refid SHM2

# Fix False tickers
tos mindist 0.5


Verifying the Install

With the NTP config in place, you can test your time source with the following command:

$ ntpq -crv -pn

remote refid st t when poll reach delay offset jitter
o127.127.22.0 .PPS. 0 l 9 16 377 0.000 -0.020 0.001
* .GPS. 1 l 8 16 377 0.000 -17.968 19.808
+ .SHM2. 0 l 7 16 377 0.000 -0.020 0.001

The ‘o’ next to the PPS entry indicates that ntp is working and using kernel mode PPS pulses as the timesource.  This is exactly what we want.

Now the time services can take a few minutes for the ‘jitter’ to drop when you first boot up. Mine is at .001 within 5 to 10 minutes after boot. If you used a battery in the GPS hat, this should help lower the wait time to get to .001. But a 5 minute after bootup wait time is good.


Notes and Explanations

So let’s connect the dots on what’s happening here now that we have a working config.

GPSD and NTP will share memory spaces.
You can see the shared memory spaces in use like this:

 ipcs -m 

Output looks like this:
Which will display something like this
—— Shared Memory Segments ——–
key shmid owner perms bytes nattch status
0x4e545030 0 root 600 80 2
0x00000000 98305 pi 600 33554432 2 dest
0x00000000 196610 pi 600 524288 2 dest
0x00000000 229379 pi 600 393216 2 dest
0x4e545031 262148 root 600 80 1
0x4e545032 294917 root 666 80 1
0x4e545033 327686 root 666 80 1
0x4e545034 360455 root 666 80 1
0x4e545035 393224 root 666 80 1
0x4e545036 425993 root 666 80 1
0x4e545037 458762 root 666 80 1
0x47505344 491531 root 666 8928 1
Convert key column value hex to ASCII to get segment reference name
0x4e545030 is “NTP0” uses GPS in-band serial data stream (least accurate)
0x4e545031 is “NTP1” uses GPS in-band + PPS out of band (a little better accuracy)
0x4e545032 is “NTP2” same as NTP0 and used when gpsd not run as root
0x4e545033 is “NTP3” same as NTP1 and used when gpsd not run as root

The 127.127.x.x address you see in the output and that were used in the config come from GPSD drivers.
GPSD and NTP are using a shared memory resource ‘SHM’ and there are 4 shared memory spaces. These are accessed via,,, and

You’ll notice that in the ntp config we are using and as sources.  These are the GPSD provided interfaces.
GPSD also enables the PPS kernel mode driver and is accessed via
This command displays the NTP sources and stats about the sources:
remote refid st t when poll reach delay offset jitter
o127.127.22.0 .PPS. 0 l 9 16 377 0.000 -0.020 0.001
* .GPS. 1 l 8 16 377 0.000 -17.968 19.808
+ .SHM2. 0 l 7 16 377 0.000 -0.020 0.001

The 2nd column (.PPS., .GPS., and .SHM2.) are just labels configured in the ntp.conf file.

The .PPS. has an ‘o’ in front of the line indicating that the NTP service is using kernel mode PPS as the time source. This is what we want. Notice that the last column is the ‘jitter’. The smaller the number, the better. And in this output, PPS source has the smallest possible value of 1 microsecond.  It can’t get any better than that.

Now in the ntp.conf file, you notice some things that are not ordinarily seen in a normal conf.   First, I’m not using any public time source. I specifically set ‘true’ on the PPS interface

#PPS Kernel mode
server minpoll 4 maxpoll 4 true

This tells the config to use this entry as the source, no exceptions.   In a normal NTP setup, you would allow the ntp service to make the selection based on it’s selection algorithm.   Setting an entry to ‘true’ overrides ntp’s selection making process.   When using public ntp hosts, this is a bad idea, but in our case, we only want to use GPS as the source.

Now I did try various combinations in the ntp.conf using gpsd drivers and public hosts as sources. I found that the config above was the fastest and most reliable way to get the PPS source.

Tagged , , . Bookmark the permalink.

30 Responses to How to Setup a GPS PPS NTP Time server on Raspberry Pi

  1. Thanks for the tutorial. Quick note about a typo at the end of the post: “way to get the the PPS source.” Not a big deal but it bugged me 🙂

  2. John says:

    Great tutorial, I found it very useful and easy to follow.

    It looks like the version of NTP fetched by ‘apt-get install ntp’ was compiled with the necessary support for kernel mode PPS, so I didn’t bother compiling it from source code. The .PPS. line reported by ‘ntpq -pn’ begins with an ‘o’, and the jitter eventually decreases to 0.001 or 0.000, which indicates that kernel mode PPS is working with the ‘stock’ version of ntp.

    Thanks for posting this tutorial!

  3. Manuel says:

    Great tutorial. One thing I’d like to ask is regarding the other devices in my local network. I would like all my home network to sync with the pi that has the GPS.

    Should the other devices (I guess I should call them NTP clients) have their own ntp.conf files with the line: server iburst, for example? Or should they be pointing to the local IP address of the pi that has the GPS?

    Thanks in advance.

    • mike says:

      You can have all the other devices run the ntp daemon. Use the the IP of the Pi as the source. Or you can go old school and cron ntpdate to run every 15 minutes using the pi as the source. Both get you the same end result.

  4. Steve Sykes says:

    I believed I have followed the directions correctly but when in comes to verifying the gps on console, I have a problem.

    There is no /dev/gps* so the test ends up with
    “GPS NOT mapped to console port”

    What have I missed? The install is a fresh install of Raspbian Stretch on a Pi 2 B. The gps is wotking as I get the correct response from cgps.

    Steve KD2OM

  5. Steve Sykes says:

    I have now started again with a new install of Stretch. I followed the instructions exactly.
    Doing the tests I get from user login.

    pi@radiopi:~ $ cat /dev/ttyAMA0
    cat: /dev/ttyAMA0: Device or resource busy


    pi@radiopi:~ $ TESTPPS=$(ls /dev/pps*); if [ “$TESTPPS” == “/dev/pps0” ]; then echo ‘NTP PPS kern mode interrupt line device present’; else echo ‘NTP PPS Device NOT present’; fi
    NTP PPS Device NOT present

    But ntpq –crv -pn

    pi@radiopi:~ $ ntpq -crv -pn
    associd=0 status=0115 leap_none, sync_pps, 1 event, clock_sync,
    version=”ntpd 4.2.8p12@1.3728 Thu Nov 22 01:32:39 UTC 2018 (1)”,
    processor=”armv7l”, system=”Linux/4.14.79-v7+”, leap=00, stratum=1,
    precision=-20, rootdelay=0.000, rootdisp=500.105, refid=PPS,
    reftime=dfa08d6f.030bfa06 Wed, Nov 21 2018 21:05:35.011,
    clock=dfa08d76.7342e2db Wed, Nov 21 2018 21:05:42.450, peer=65188, tc=4,
    mintc=3, offset=-3.870325, frequency=-120.557, sys_jitter=3.641325,
    clk_jitter=5.427, clk_wander=1.573

    remote refid st t when poll reach delay offset jitter
    o127.127.22.0 .PPS. 0 l 7 16 377 0.000 -3.870 3.641
    * .GPS. 1 l 4 16 377 0.000 59.463 30.612 .SHM2. 0 l – 16 0 0.000 0.000 0.000

    It looks like it is working but why do the tests fail.

    Steve KD2OM

    • Brent says:

      Just found this great article! I happen to run into the same issue you have Steve. if you do a ls -l /dev/pps* you will probably see pps0 and pps1 which will cause the test to fail.

  6. pad says:

    Good write up. However, it appears one step is out of order. At least when I was setting my system the DHCP client hooks folder wasn’t created until after installing NTP and you rename this folder before this step.

  7. Rob Ramsey says:

    I’ve been searching the internet for a good GPS fed NTP setup procedure for days. This is the only guide that actually worked. Thank you, thank you, thank you!

  8. Ingemar Lekteus says:

    I have followed these excellent instructions, but I must have done something wrong. There is an error when the Pi starts:
    systemd[1]: [/lib/systemd/system/gpsd.socket:6] Failed to parse address value, ignoring: [::1]:2947
    and there is no output from cat /dev/ttyAMA0.
    Any suggestions?

  9. Bert says:

    Absolute noob here,
    For years i had a timeserver based on David’s Taylor’s Original setup, That broke a few days ago, So i bought a new one (RPI3B+) and immediatly encountered big problems. Most of the GPStimeserver project pages are outdated to say the least. Then i found this page, liked the look of the instructions and tried to install a new timeserver. But i got nowhere : the “cat /dev/ttyAMA0” command did not show any nmea data. After some research i found out that there is a big difference between a PI3(B) and a PI3B+: The last ons has bluetooth and the is tied to ttyAMA0. On another project site that gives instructions for a PI3B+ i found the solution:
    BEFORE you apply the instructionset you have to give a few commands/add few lines (3 ) to the “/boot/config.txt” file, reboot and then follow the original instructions. Here are the extra steps you have to do:

    # Disable & steal ttyAMA0
    systemctl stop serial-getty@ttyAMA0.service
    systemctl disable serial-getty@ttyAMA0.service

    # Disable Bluetooth and steal the hardware UART
    sudo nano /boot/config.txt
    # move Bluetooth to /dev/uart1 (possible not needed)

    # (Optional: happens later in the original script)Remove “console-serial,115200” from /boot/cmdline.txt

    #and reboot
    sudo reboot

    # info:

    The new timeserver is at this moment up and running for more than 48 hours and doing absolute fine.

  10. Bert says:

    Aaaargh a typo:
    read the sentence
    “The last ons has bluetooth and the is tied to ttyAMA0.”
    “The last one has bluetooth and is tied to ttyAMA0. ”

  11. Peter Barwich says:

    I’ve tried it with and without public ntp servers (pool), and with and without ‘true’ on the PPS lines in ntp.conf. In all cases ntpq -p on the pi looks good and it homes in on a low offset, low jitter lock. However downstream clients refuse to use it. Any idea what I’ve missed?

    • mike says:

      In the ntp.conf, you would need to allow inbound ntp traffic. IN these examples, it’s all outbound.

      You’d need something like this:
      # By default, exchange time with everybody, but don’t allow configuration.
      restrict -4 default kod notrap nomodify nopeer noquery limited
      restrict -6 default kod notrap nomodify nopeer noquery limited

      You can search for more examples on setting up ntpd as a server.

  12. Pingback: Mysterious GPSD | Wireless Jon

  13. Peter Barwich says:

    Actually I had this
    “restrict -4 default notrap nomodify nopeer noquery limited”
    I commented out the IPv6 line as I don’t use it. Downstream clients can see the pi server, report its offset and jitter, but use another alternative. Thanks for getting back though.

  14. Afonso Klein says:

    In recent version of raspbian, I found NMEA messages in /dev/ttyS0 instead of AMA0! Worked for me.

  15. Afonso Klein says:

    In recent version of raspbian, I found NMEA messages in /dev/ttyS0 instead of AMA0! Worked for me…

  16. veysel says:

    Hello, thank you for sharing. I have a question, for example how can we entegrate rtc module to this ntp if there is no satellite connection ?

  17. Bill Devanney W7WLD says:

    Really impressive & confirms my theory that astronomers are the smartest folks in all the sciences! I came across this site because my local ISP (AT&T) is prone to shutting down UDP access to Port 123 for days at a time, which causes all the NTP-based clocks in my house to drift (Windows, OpenSprinkler system, Lutron, etc.). Can’t have that. So I needed my own ntp time server, which led to Amazon ($600+ for an OTS model, nope), which led to the Adafruit GPS Hat, which eventually led to your your site (following my theory on astronomers). My only concern going into it was my Pi is a Raspberry Pi Model A+ not a Model 3B, wouild it still work? After going through each installation step EXACTLY — I found nope, it didn’t work. No results for “cgps -s”. But tinkering around I managed to device this A+ specific patch:

    For a Raspberry Pi Model A+ installation the following line:

    sudo sed -i ‘s/console=serial,115200//g’ /boot/cmdline.txt

    should be modified to:

    sudo sed -i ‘s/console0=serial,115200//g’ /boot/cmdline.txt

    And it all works! Completely! One small change for mankind, one big leap to working around the AT&T Port 123 block. For others following behind me the only other comment I would make is to be patient, some of the update & recompile commands steps take a long time, and of course everything needs to be done with the power of sudo su.

  18. Don Froula says:

    A very nice guide, and it all seems to work well! I found that the latest version of ntp installed with “apt install ntp” worked fine. No need to customize.

    I am curious why you use NTP’s built-in kernel driver at as the reference source rather than GPSD’s shared memory PPS source? I had heard there were issues with the way NTP applied the PPS pulse timing to the NMEA data in its own driver.

    Most Raspberry Pi NTP server instructions say to use only the two GPSD shared memory pseudo-IPs at and

    Are GPSD and the GPSD sources even required?


  19. Don Froula says:

    After more reading, I think I have some clues as to what is going on with the configuration.

    The PPS source at uses NTP driver “22” (the third octet of the pseudo-IP), which is the “PPS Clock Discipline” NTP driver. The docs at state that this driver has no notion of absolute seconds and must be used with another source of absolute time, set as the “prefer” source in ntp.conf. “flag3” tells it to use the kernel mode PPS driver. The driver then can provide disciplined absolute time.

    The other two sources use the NTP “28” driver, which is the shared driver used by GPSD. This driver provides two clock sources. One is at SHM0 and contains NMEA time only. This is the “prefer” absolute time source needed by NTP driver “22” and is set as such in ntp.config.

    The other NTP “28” driver source is at SHM2, which is also the PPS kernel data, but mediated through GPSD. It appears to be functionally identical to the driver “22” PPS source. I’m wondering if it is actually required and if NTP can use the SHM0 data to provide a disciplined time source, as driver “22” does.

    After settling, both the driver “22” and driver “28, SHM2” show identical delays and jitter using “ntpq -pn”.

    About right?



  20. Don Froula says:

    More reading….

    Looks like the NTP “22” PPS driver is activated by GPSMON, but needs the “28” NMEA driver set as preferred. NTP itself combines the NMEA data on “28.0” with the kernel PPS timing from “22.0” to create a disciplined absolute time source on “22.0”, which is hard set to be the reference timing source with the “true” flag.. Is similar to but combining the PPS info with the NMEA data is done by GPSMON before passing to NTP, not by NTP itself. The recommended configuration sets this as the backup timing source.

    The two PPS sources seem to perform the same, but use different ways of combining the kernel PPS tick and the NMEA data, one by GPSMON, the other by NTP.


Leave a Reply

Your email address will not be published. Required fields are marked *

Solve : *
17 × 16 =