Skip to content

Instantly share code, notes, and snippets.

@p0lr
Last active June 9, 2025 19:19
Show Gist options
  • Save p0lr/ce2203cf6e23c2c95b556ea20f0144f7 to your computer and use it in GitHub Desktop.
Save p0lr/ce2203cf6e23c2c95b556ea20f0144f7 to your computer and use it in GitHub Desktop.

Setting up an NTP server on a Raspberry Pi

Necessary Components

Notes:

  • I prefer the Raspberry Pi Zero 2 W with Headers for this project
  • You will also need to be able to read an write to the MicroSD card on another system to flash the OS
  • You may need to solder header pins onto the GPS

1. Install Raspberry Pi OS

The fine folks at the Raspberry Pi Foundation have already created a bunch of documentation on how to install Raspberry Pi OS onto a MicroSD card. Check out the following link and follow their installation instructions noting the pre-configuration options below that will need to be set:

https://www.raspberrypi.com/software/

Make sure to pre-configure the following during the installation process:

  • Username
  • Password
  • Hostname
  • WiFi
  • Enable SSH

2. Initial Boot

  • Insert the MicroSD card with the Raspberry Pi OS image into the MicroSD slot on the Raspberry Pi Zero 2 W
  • Connect the MicroUSB power supply

3. Determine the IP Address

The Raspberry Pi should connect to the WiFi network that you pre-configured. It will receive an IP address via DHCP once connected. You will need to interrogate the DHCP server to determine the IP address provided to the device with the hostname that you pre-configured in the Raspberry Pi OS installation.

4. SSH to the Raspberry Pi

  • At the command prompt, type ssh pi@<ip address of your Raspberry Pi>
  • You should be asked for the password you provided during the Raspberry Pi OS installation.
  • Once you enter the password, you should see a command prompt similar to the following:

pi@time:~ $

5. Modify the Raspberry Pi Config

At the command prompt, type sudo nano /boot/firmware/config.txt

In the [all] section at the end of the file, add the following lines:

enable_uart=1
dtoverlay=pps-gpio,gpiopin=18

When finished, it should look similar to the following:

# For more options and information see
# http://rptl.io/configtxt
# Some settings may impact device functionality. See link above for details

# Uncomment some or all of these to enable the optional hardware interfaces
#dtparam=i2c_arm=on
#dtparam=i2s=on
#dtparam=spi=on

# Enable audio (loads snd_bcm2835)
# dtparam=audio=on

# Additional overlays and parameters are documented
# /boot/firmware/overlays/README

# Automatically load overlays for detected cameras
# camera_auto_detect=1

# Automatically load overlays for detected DSI displays
# display_auto_detect=1

# Automatically load initramfs files, if found
auto_initramfs=1

# Enable DRM VC4 V3D driver
dtoverlay=vc4-kms-v3d
max_framebuffers=2

# Don't have the firmware create an initial video= setting in cmdline.txt.
# Use the kernel's default instead.
disable_fw_kms_setup=1

# Run in 64-bit mode
arm_64bit=1

# Disable compensation for displays with overscan
# disable_overscan=1

# Run as fast as firmware / board allows
arm_boost=1

[cm4]
# Enable host mode on the 2711 built-in XHCI USB controller.
# This line should be removed if the legacy DWC2 controller is required
# (e.g. for USB device mode) or if USB support is not required.
# otg_mode=1

[cm5]
# dtoverlay=dwc2,dr_mode=host

[all]
enable_uart=1
dtoverlay=pps-gpio,gpiopin=18

6. Disable the Console attached to ttyS0

At the command prompt, type sudo nano /boot/firmware/cmdline.txt

Remove the following:

console=serial0,115200

When finished, it should look as follows:

console=tty1 root=PARTUUID=87117695-02 rootfstype=ext4 fsck.repair=yes rootwait cfg80211.ieee80211_regdom=US

7. Enable the PPS Module

At the command prompt, type sudo nano /etc/modules

Add the following line:

pps-gpio

When finished, it should look similar to the following:

# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.
# Parameters can be specified after the module name.

pps-gpi

Reboot the system:

At the command prompt, type sudo reboot

8. Set A Static IP Address

At the command prompt, type sudo nmcli c modify "preconfigured" ipv4.addresses "192.168.0.3/24"

This change takes efect immediately. If you are connected to the system via network, you may need to reconnect on the new IP address.

9. Connect GPS

Raspberry Pi General Purpose Input Output (GPIO) Pinout Raspberry Pi GPIO Pinout

Connecting the GPS to the Raspberry Pi is pretty straightforward:

GPS Raspberry Pi Pin Raspberry Pi Signal
PPS 12 GPIO 18
VIN 4 5V
GND 6 GND
RX 8 UART TXD
TX 10 UART RXD

10. Test GPS

At the command prompt, type gpsmon --nocurses /dev/ttyS0

The output should look similar to the following:

gpsmon: time:/dev/ttyS0 9600 8N1
(84) $GPGGA,015638.000,3956.3013,N,10502.8197,W,2,08,1.12,1620.8,M,-20.9,M,0000,0000*6C
(58) $GPGSA,A,3,31,32,01,25,10,28,02,03,,,,,1.43,1.12,0.89*08
(70) $GPRMC,015638.000,A,3956.3013,N,10502.8197,W,0.08,8.98,040625,,,D*7D
(37) $GPVTG,8.98,T,,M,0.08,N,0.15,K,D*3D
(84) $GPGGA,015639.000,3956.3014,N,10502.8197,W,2,08,1.23,1620.8,M,-20.9,M,0000,0000*68
(58) $GPGSA,A,3,31,32,01,25,10,28,02,03,,,,,2.27,1.23,1.91*03
(70) $GPRMC,015639.000,A,3956.3014,N,10502.8197,W,0.11,8.98,040625,,,D*73
(37) $GPVTG,8.98,T,,M,0.11,N,0.21,K,D*32
(84) $GPGGA,015640.000,3956.3014,N,10502.8197,W,2,08,1.12,1620.8,M,-20.9,M,0000,0000*64
(58) $GPGSA,A,3,31,32,01,25,10,28,02,03,,,,,1.43,1.12,0.89*08
(70) $GPRMC,015640.000,A,3956.3014,N,10502.8197,W,0.13,8.98,040625,,,D*7F
(37) $GPVTG,8.98,T,,M,0.13,N,0.25,K,D*34

Press CTRL-C to stop the test.

11. Test PPS Signal

At the command prompt, type ppstest /dev/pps0

The output should look similar to the following:

trying PPS source "/dev/pps0"
found PPS source "/dev/pps0"
ok, found 1 source(s), now start fetching data...
source 0 - assert 1748696194.448467356, sequence: 1741 - clear  0.000000000, sequence: 0
source 0 - assert 1748696195.448470325, sequence: 1742 - clear  0.000000000, sequence: 0
source 0 - assert 1748696196.448474439, sequence: 1743 - clear  0.000000000, sequence: 0
source 0 - assert 1748696197.448476314, sequence: 1744 - clear  0.000000000, sequence: 0
source 0 - assert 1748696198.448481522, sequence: 1745 - clear  0.000000000, sequence: 0
source 0 - assert 1748696199.448485479, sequence: 1746 - clear  0.000000000, sequence: 0
source 0 - assert 1748696200.448487875, sequence: 1747 - clear  0.000000000, sequence: 0
source 0 - assert 1748696201.448491625, sequence: 1748 - clear  0.000000000, sequence: 0

Press CTRL-C to stop the test.

12. Install GPSD

At the command prompt, type sudo apt install gpsd -y

13. Configure GPSD

At the command prompt, type sudo nano /etc/default/gpsd

Modify the file to include only the following lines:

DEVICES="/dev/ttyS0 /dev/pps0"
GPSD_OPTIONS="-n -r -b -G"

What do these options do?

From gpsd --help:

  Options include: 
  -?, -h, --help            = help message
  -b, --readonly            = bluetooth-safe: open data sources read-only
  -D, --debug integer       = set debug level, default 0 
  -F, --sockfile sockfile   = specify control socket location, default none
  -f, --framing FRAMING     = fix device framing to FRAMING (8N1, 8O1, etc.)
  -G, --listenany           = make gpsd listen on INADDR_ANY
  -l, --drivers             = list compiled in drivers, and exit.
  -n, --nowait              = don't wait for client connects to poll GPS
  -N, --foreground          = don't go into background
  -P, --pidfile pidfile     = set file to record process ID
  -p, --passive             = do not reconfigure the receiver automatically
  -r, --badtime             = use GPS time even if no fix
  -S, --port PORT           = set port for daemon, default 2947
  -s, --speed SPEED         = fix device speed to SPEED, default none
  -V, --version             = emit version and exit.

When finished, it should look similar to the following:

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

# Other options you want to pass to gpsd
GPSD_OPTIONS="-n -r -b --listenany"

14. Restart GPSD

At the command prompt, type sudo systemctl restart gpsd

15. Verify GPSD

At the command prompt, type gpsmon --nocurses

The output should look similar to the following:

gpsmon: tcp://localhost:2947
(82) {"class":"VERSION","release":"3.22","rev":"3.22","proto_major":3,"proto_minor":14}
(292) {"class":"DEVICES","devices":[{"class":"DEVICE","path":"/dev/ttyS0","driver":"NMEA0183","activated":"2025-05-31T13:10:40.106Z","flags":1,"native":0,"bps":9600,"parity":"N","stopbits":1,"cycle":1.00},{"class":"DEVICE","path":"/dev/pps0","driver":"PPS","activated":"2025-05-31T13:10:40.451Z"}]}
(122) {"class":"WATCH","enable":true,"json":false,"nmea":false,"raw":2,"scaled":false,"timing":false,"split24":false,"pps":true}
TOFF= 1748697040.939376513 real= 1749002443.000000000
(84) $GPGGA,020043.000,3956.3091,N,10502.8191,W,2,08,1.13,1623.5,M,-20.9,M,0000,0000*63
(58) $GPGSA,A,3,31,32,01,25,10,28,02,03,,,,,1.44,1.13,0.89*0E
(72) $GPRMC,020043.000,A,3956.3091,N,10502.8191,W,0.32,221.37,040625,,,D*78
(39) $GPVTG,221.37,T,,M,0.32,N,0.60,K,D*3A
PPS= 1748697041.45133135 clock= 1749002444.00000000 offset=-305402.548668644
------------------- PPS offset: -305402.548668644 ------
TOFF= 1748697041.968524741 real= 1749002444.000000000
(84) $GPGGA,020044.000,3956.3090,N,10502.8191,W,2,08,1.13,1623.5,M,-20.9,M,0000,0000*65
(58) $GPGSA,A,3,31,32,01,25,10,28,02,03,,,,,1.44,1.13,0.89*0E
(72) $GPRMC,020044.000,A,3956.3090,N,10502.8191,W,0.36,221.37,040625,,,D*7A
(39) $GPVTG,221.37,T,,M,0.36,N,0.67,K,D*39
PPS= 1748697042.45133437 clock= 1749002445.00000000 offset=-305402.548665623
------------------- PPS offset: -305402.548665623 ------
TOFF= 1748697042.970956095 real= 1749002445.000000000
(84) $GPGGA,020045.000,3956.3089,N,10502.8191,W,2,08,1.13,1623.5,M,-20.9,M,0000,0000*6C

Press CTRL-C to stop the test.

You can also use the CLI GPS application to test GPS communication

At the command prompt, type cgps -s

The output should look similar to the following:

┌─ssssssssssssssssssssssssssssssssssssssssss┐┌─aaaaaaaaaaaaaaaaaSeen 12/Used  8┐
│ Time:        2025-06-04T02:16:13.000Z (0) ││GNSS   PRN  Elev   Azim   SNR Use│
│ Latitude:         39.93839167 N           ││GP  1    1  40.0  292.0  27.0  Y │
│ Longitude:       105.04698667 W           ││GP  2    2  43.0  252.0  31.0  Y │
│ Alt (HAE, MSL):   1606.700,   1627.600 m  ││GP  3    3  23.0  311.0  17.0  Y │
│ Speed:             0.44 km/h              ││GP 10   10  34.0  122.0  18.0  Y │
│ Track (true, var):   189.0,   8.1     deg ││GP 25   25  21.0   71.0  18.0  Y │
│ Climb:             0.00 m/min             ││GP 28   28  77.0  109.0  34.0  Y │
│ Status:         3D DGPS FIX (82 secs)     ││GP 31   31  58.0  189.0  27.0  Y │
│ Long Err  (XDOP, EPX):  0.58, +/-  2.2 m  ││GP 32   32  49.0   55.0  22.0  Y │
│ Lat Err   (YDOP, EPY):  1.07, +/-  4.0 m  ││GP  4    4   1.0  261.0   0.0  N │
│ Alt Err   (VDOP, EPV):  0.90, +/-  5.2 m  ││GP 12   12   7.0   41.0  18.0  N │
│ 2D Err    (HDOP, CEP):  1.11, +/-  5.3 m  ││GP 26   26   8.0  164.0  16.0  N │
│ 3D Err    (PDOP, SEP):  1.43, +/-  6.8 m  ││SB135   48  39.0  209.0  31.0  N │
│ Time Err  (TDOP):       1.41              ││                                 │
│ Geo Err   (GDOP):       2.78              ││                                 │
│ ECEF X, VX:              n/a    n/a       ││                                 │
│ ECEF Y, VY:              n/a    n/a       ││                                 │
│ ECEF Z, VZ:              n/a    n/a       ││                                 │
│ Speed Err (EPS):       +/- 29.0 km/h      ││                                 │
│ Track Err (EPD):        n/a               ││                                 │
│ Time offset:           -305412.866757769 s││                                 │
│ Grid Square:            DM79lw45          ││                                 │
└─aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa┘└─eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee┘

Press CTRL-C to exit the test.

16. Install NTPD

At the command prompt, type sudo apt install ntp -y

17. Configure NTPD

At the command prompt, type sudo nano /etc/ntpsec/ntp.conf

Ensure these options are set:

# /etc/ntpsec/ntp.conf, configuration for ntpd; see ntp.conf(5) for help

driftfile /var/lib/ntpsec/ntp.drift
leapfile /usr/share/zoneinfo/leap-seconds.list
tos maxclock 7

server 127.127.28.0 minpoll 4 
fudge 127.127.28.0 refid GPS

server 127.127.28.1 minpoll 4 prefer
fudge 127.127.28.1 refid PPS

restrict default kod nomodify nopeer noquery limited
restrict 127.0.0.1
restrict ::1
restrict 192.168.0.0/24

18. Restart NTPD

At the command prompt, type sudo systemctl restart ntp

19. Verify NTPD

At the command prompt, type ntpmon 127.0.0.1

The output should look similar to the following:

     remote           refid      st t when poll reach   delay   offset   jitter
*SHM(0)          .GPS.            0 l    -   16  377   0.0000  -2.3443  21.1453
 SHM(1)          .PPS.            0 l    -   16    0   0.0000   0.0000   0.0010
ntpd ntpsec-1.2.2                              Updated: 2025-06-04T07:51:01 (8)
 lstint avgint rstr r m v  count    score   drop rport remote address
      0   1463    0 . 6 2     22    0.450      0 53543 localhost

Press CTRL-C to exit the test.

20. Update System Time Sync

At the command prompt, type sudo nano /etc/systemd/timesyncd.conf

Modify the NTP line as follows:

[Time]
NTP=127.0.0.1

Reboot the system: At the command prompt, type sudo reboot

Installation is Complete!

@devsecfranklin
Copy link

Nice write-up! I followed it but with slightly different hardware and it totally worked! Also I added an optional step 21. You just add this line to the end of /etc/ntpsec/ntp.conf:

# Uncomment if you want to provide time to your local subnet
broadcast 10.10.15.255

@p0lr
Copy link
Author

p0lr commented Jun 7, 2025 via email

@devsecfranklin
Copy link

Interesting. Reading up on this, many operating systems do not support broadcast NTP.

I suppose because grabbing random broadcast messages is some sort of security issue :)

What would it take to add in Certbot to the mix? It would be cool to use the TLS certs, even though I am not super fond of my internal hosts "phoning home" to the EFF every day.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment