Build a Stratum 1 NTP Time Server with Raspberry Pi and GPS
Every device on a home network gets its clock from an NTP server somewhere upstream, usually a pool of public internet time servers several network hops away with tens of milliseconds of jitter. That's plenty accurate for browsing and email, but it isn't good enough for precise packet timestamping, ham radio digital modes, security camera correlation, or just the satisfaction of running infrastructure most people don't know exists. A Raspberry Pi with a GPS receiver and its 1-pulse-per-second (PPS) output becomes a Stratum 1 NTP server — disciplined directly by the GPS satellite constellation's atomic clocks rather than by another server's opinion of the time — and can serve sub-millisecond accuracy to every other device on the local network.
Why GPS Specifically
GPS satellites carry atomic clocks and broadcast time as part of their navigation signal, accurate to tens of nanoseconds. The GPS module's serial NMEA output alone is only accurate to within a second or so due to processing and transmission delay, but the PPS pin — a hardware pulse that fires at the exact start of each UTC second — is accurate to tens of nanoseconds once the receiver has a satellite lock. Combining the NMEA sentence (which second it is) with the PPS edge (exactly when that second starts) gives the Pi a time reference that's dramatically better than anything reachable over the internet.
Wiring the GPS Module
GPS module pinRaspberry Pi GPIO pin VCC3.3V (physical pin 1) GNDGND (physical pin 6) TX (module) → RX (Pi)GPIO 15 / RXD (physical pin 10) RX (module) ← TX (Pi)GPIO 14 / TXD (physical pin 8) PPSGPIO 18 (physical pin 12) — or any free GPIO, configured belowBase OS Configuration
- Flash Raspberry Pi OS Lite (64-bit, headless) with Raspberry Pi Imager, enabling SSH in the imager's advanced options before first boot.
- Disable the serial console (so the OS doesn't fight the GPS module for the UART) but keep the hardware UART enabled: run sudo raspi-config → Interface Options → Serial Port → "No" to login shell over serial, "Yes" to hardware serial enabled.
- Edit /boot/firmware/config.txt and add:
dtoverlay=pps-gpio,gpiopin=18
enable_uart=1 - Reboot, then confirm the PPS device exists: ls /dev/pps0 should return without error.
Installing GPS and PPS Tools
- Install the required packages: sudo apt update && sudo apt install -y gpsd gpsd-clients pps-tools chrony
- Test the raw PPS signal is arriving: sudo ppstest /dev/pps0 should print a steady stream of timestamps roughly one second apart once the GPS has a lock (a lock can take several minutes outdoors, longer near a window, and won't happen indoors away from any window at all).
- Configure gpsd to use the correct serial device by editing /etc/default/gpsd: set DEVICES="/dev/ttyAMA0" (or /dev/serial0 depending on Pi model) and GPSD_OPTIONS="-n".
- Verify NMEA data is flowing with cgps -s — watch for a 3D fix and a reasonable satellite count (6+ is solid) before moving on.
Configuring Chrony as the Stratum 1 Source
Edit /etc/chrony/chrony.conf and add:
refclock SHM 0 offset 0.0 delay 0.2 refid GPS noselect refclock PPS /dev/pps0 refid PPS lock GPSThis tells chrony to use the coarse NMEA time (via gpsd's shared memory segment) only to identify which second it is, then discipline the actual clock against the much more precise PPS edge, locked to the GPS reference. Restart chrony (sudo systemctl restart chrony) and after a few minutes check status with chronyc sources -v and chronyc tracking — a healthy setup shows the PPS refclock selected with a system time offset in the tens of microseconds or better.
Serving Time to the Local Network
Add an allow line to chrony.conf for your LAN subnet, e.g. allow 192.168.1.0/24, then restart chrony again. Point other devices — a home server, a NAS, security camera NVR, or another Pi — at this Pi's IP address as their NTP source, either individually or via a DHCP option on the router so the whole network picks it up automatically.
Verifying Accuracy
CheckCommandWhat to look for GPS fix qualitycgps -s3D fix, 6+ satellites PPS signal presentsudo ppstest /dev/pps0Steady ~1 second interval timestamps Chrony source selectionchronyc sources -vPPS refclock marked with an asterisk (currently selected) System clock offsetchronyc tracking"System time" offset in the microsecond range Stratum levelchronyc trackingStratum 1 (this Pi is now a primary reference itself)Troubleshooting
- No GPS fix indoors — GPS needs a reasonably clear sky view; a window-mounted active antenna usually works, but a windowsill deep inside a building or a metal roof overhead will prevent a lock entirely.
- PPS device missing after reboot — double check the dtoverlay line syntax in config.txt and that the GPIO pin number matches the module's actual PPS wire.
- Chrony never selects the PPS source — the lock GPS directive requires the SHM refclock to be present and marked noselect; a typo in either refclock line breaks the pairing silently.
Once running, this box quietly becomes the most accurate clock on the network, disciplined by satellites rather than by trust in an upstream server several hops away. It's a genuinely small build — a GPS module, some GPIO wiring, and a chrony config — for a piece of infrastructure that most home networks never have and would rarely miss, right up until precise timestamps actually matter.
Related Guides
- GPS Modules for Maker Projects: NEO-6M/NEO-M8N Wiring, NMEA Parsing, and Logging with ESP32
- Build a Raspberry Pi Dashcam: Continuous Loop Recording, GPS Overlay, and Impact-Triggered Save
- How to Install Klipper on Any 3D Printer: Complete Setup Guide
- How to Set Up OpenCV Machine Vision on a Raspberry Pi
- Raspberry Pi: Complete Headless Setup Guide (No Monitor Needed)
- Raspberry Pi: Headless OS Setup
- How to Set Up a Raspberry Pi Headless with SSH and WiFi
- How to Install and Configure Pi-hole on Raspberry Pi