Setting up Chrony/NTP Server with GPS on Ubuntu 22.04

Introduction

In this post I will cover setting up a ntp/chrony server on Ubuntu 22.04 using a Satellite GPS Receiver. I have also recently published another post on Chrony, which goes a bit more into basic commands. That post can be found here.


Hardware

Note: the GlobalSat BU-353-W11 does not support 1pps. It’s designed primarily for positioning and navigation, not precision timing. We can use it as our timesource for chrony, with a typical accuracy: ±50–150 ms. Good enough for a lab.


Hardware Detection

After plugging in the USB cable on the GNSS module, check for a new USB device.

# lsusb
Bus 001 Device 004: ID 1546:01a7 U-Blox AG [u-blox 7]

Output from dmegs also shows the device was detected properly

[347055.572498] usb 1-2.4: new full-speed USB device number 4 using tegra-xusb
[347055.768706] cdc_acm 1-2.4:1.0: ttyACM0: USB ACM device
[347055.768784] usbcore: registered new interface driver cdc_acm
[347055.768788] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters

In the output above you can see the device was detected ttyACM0. Now we need to confirm how the device was enumerated. As you can see the device is owned by root and the group is dialout

# ls -l /dev/ttyACM*
crw-rw---- 1 root dialout 166, 0 Dec 29 13:40 /dev/ttyACM0

You can run a quick check to confirm that the device is functioning as shown below

# cat /dev/ttyACM0
$GPTXT,01,01,02,u-blox ag - www.u-blox.com*50

$GPTXT,01,01,02,HW  UBX-G70xx   00070000 FF7FFFFFo*69

$GPTXT,01,01,02,ROM CORE 1.00 (59842) Jun 27 2012 17:43:52*59

$GPGGA,201055.00,3356.15827,N,08345.15611,W,2,12,0.80,280.0,M,-31.5,M,,0000*67

-----------truncated----------------

GPSD

To use a GPS/GNSS antenna with chronyd on Linux for time synchronization and location sharing, we will use the gpsd service to manage the GPS hardware and make its data accessible to other applications, including chronyd

Package Installation

# sudo apt update
# sudo apt install gpsd gpsd-clients chrony pps-tools 

GPSD Configuration

Identify the GPS device: The device will likely be /dev/ttyUSB0/dev/ttyACM0, or similar. You can verify this by running cat /dev/ttyUSB0 (replace ttyUSB0 with your suspected device) and looking for NMEA strings (lines starting with $GPGGA$GNRMC, etc.).

Edit the gpsd configuration file: Open /etc/default/gpsd and set the DEVICESGPSD_OPTIONS, and USBAUTO variables.

START_DAEMON="true"
GPSD_OPTIONS="-n -b"
DEVICES="/dev/ttyACM0"
USBAUTO="false"
GPSD_SOCKET="/var/run/gpsd.sock"

For reference here are configuration params for GPSD_OPTIONS

FlagArgumentMeaningNotes
-nnoneStart reading GPS immediatelyRequired for chrony/NTP
-bnoneRead-only mode (no device writes)Recommended for USB pucks
-NnoneRun in foregroundDebug only
-D<level>Debug verbosityUse -D 2 or -D 3
-F<path>Control socket pathNeeded for manual runs
-s<baud>Force serial baud rateUART devices only
-S<port>TCP listening portRare; usually disabled
-GnoneAllow remote TCP clientsAvoid on servers
-lnoneList drivers and exitDiagnostic
-VnoneShow version and exitInformational
-hnoneHelp outputReference

Enable and start gpsd

# sudo systemctl enable gpsd
# sudo systemctl restart gpsd

Verify gpsd is receiving data

# cgps -s

Output below

Screen output from a GPSD client showing real-time GPS data, including time, latitude, longitude, altitude, speed, and satellite information.

Interpreting the output from cgps

High-Level Status (The Big Picture)

  • (Satellites) Seen / Used: 13 / 11
  • Fix: 3D DGPS FIX – means the receiver has solved all three spatial dimensions: (Latitude, Longitude, Altitude)
  • Time is valid
  • Position accuracy: ~5–10 feet horizontal

Satellite Section (Right Pane)

Seen 13 / Used 11

This means:

  • The receiver can currently see 13 satellites
  • 11 of those are strong enough to be used in the solution

This is quite a healthy signal for a device sitting inside up against a window.

Anything above:

  • 4 used → valid 3D fix
  • 8+ used → very solid geometry
  • 10–12 used → excellent (we are here)

Constellations

  • GP = GPS
    • U.S. GPS constellation
    • Medium Earth Orbit (≈20,200 km)
    • ~30 active satellites worldwide
  • SB = SBAS (WAAS corrections)
    • Satellite-Based Augmentation System
    • Broadcast correction data
    • Improve accuracy of GPS measurements
    • Do not provide independent position fixes

The presence of SBAS satellites indicates differential reminder data is available, improving accuracy.

Fix & Timing (Left Pane)

Fix State

Status: 3D DGPS FIX (1 secs)
  • 3D = Latitude, longitude, altitude solved
  • DGPS = WAAS corrections applied
  • (1 secs) = Fix age (very fresh)

Time Quality

Time: 2025-12-29T20:32:22.000Z
Time offset: 0.078609772 s


Chrony

Install Chrony via apt

# apt install chrony -y

Configure Chrony

Edit /etc/chrony/chrony.conf. This config has been tested on Ubuntu 22.04, YMMV on other Linux versions.

###############################################################################
# Chrony configuration
#
# Purpose:
# – Discipline system time using a USB GPS receiver via gpsd (NMEA-only)
# – No PPS available, so accuracy is milliseconds (not microseconds)
# – Act as a low-priority NTP server for the local network
#
# Notes:
# – Chrony always operates internally in UTC
# – GPS time is provided by gpsd over a UNIX socket
###############################################################################
#——————————————————————————
# Include additional configuration snippets
#——————————————————————————
# Allows drop-in configuration files (not required, but standard on Ubuntu)
confdir /etc/chrony/conf.d
###############################################################################
# Debug / observability
###############################################################################
# Where chrony writes its own detailed log streams
logdir /var/log/chrony
# Enable detailed logs:
# – tracking: disciplined clock state (offset/freq/skew, etc.)
# – measurements: raw samples from sources (very useful for refclocks)
# – statistics: aggregate stats per source
log tracking measurements statistics
# Log when chrony makes a clock correction bigger than the thresholds
# (units: seconds). Helps correlate “why did time jump?” events.
logchange 0.5
#——————————————————————————
# GPS reference clock (via gpsd)
#——————————————————————————
# Use gpsd's UNIX socket as a reference clock.
#
# SOCK /var/run/gpsd.sock
# – Chrony does NOT talk to the GPS device directly
# – gpsd parses NMEA and provides time samples
#
# refid GPS
# – Human-readable label shown in chronyc output
#
# poll 4
# – Poll interval = 2^4 seconds = 16 seconds
#
# precision 1e-1
# – Declare expected precision (~100 ms)
# – REQUIRED for NMEA-only GPS to avoid sample rejection
#
# delay 0.2
# – Account for USB + NMEA sentence latency
#
# trust
# – Explicitly allow chrony to accept this low-precision refclock
#
#refclock SOCK /run/gpsd.sock refid GPS poll 4 precision 1e-1 delay 0.2 trust prefer
refclock SHM 0 refid GPS poll 4 precision 1e-1 delay 0.2 trust prefer
#——————————————————————————
# Standalone operation policy
#——————————————————————————
# Allow chrony to discipline the system clock even if:
# – No network NTP servers are configured
# – GPS is the only available reference
#
# Advertise this host as stratum 10 to NTP clients:
# – Prevents it from being treated as an authoritative time source
# – Avoids NTP loops
# – Appropriate for NMEA-only GPS (no PPS)
#
local stratum 2
#——————————————————————————
# Clock stepping behavior
#——————————————————————————
# Allow the system clock to be stepped (jumped) instead of slewed
# if the offset is larger than 1 second, but ONLY during startup
# and only for the first 3 updates.
#
# This prevents long convergence times on boot while avoiding
# disruptive time jumps during normal operation.
#
makestep 1.0 3
#——————————————————————————
# Frequency drift handling
#——————————————————————————
# Persist the measured frequency error of the system clock.
#
# This allows chrony to:
# – Start with a good frequency estimate after reboot
# – Converge faster
# – Free-run more accurately if all time sources disappear
#
driftfile /var/lib/chrony/chrony.drift
#——————————————————————————
# NTP server behavior (serving time to clients)
#——————————————————————————
# Allow NTP clients from the local subnet to query this server
#
allow 10.1.10.0/24
# Bind NTP service explicitly to this interface/address
# (prevents listening on unintended interfaces)
#
bindaddress 10.1.10.11
#——————————————————————————
# Optional dynamic source handling
#——————————————————————————
# Allow chrony to use NTP servers provided via DHCP (if present)
#
sourcedir /run/chrony-dhcp
# Allow additional NTP source files to be added modularly
#
sourcedir /etc/chrony/sources.d
#——————————————————————————
# Security and key material
#——————————————————————————
# File containing NTP authentication keys (if used)
#
keyfile /etc/chrony/chrony.keys
# Directory used to store NTS (Network Time Security) cookies and keys
#
ntsdumpdir /var/lib/chrony
#——————————————————————————
# Logging
#——————————————————————————
# Directory where chrony logs are written
#
logdir /var/log/chrony
# Uncomment the following line to enable detailed logging:
# log tracking measurements statistics
#——————————————————————————
# Stability and safety controls
#——————————————————————————
# Prevent chrony from applying updates if clock estimates become unstable
#
maxupdateskew 100.0
# Periodically sync the system time back to the hardware RTC
# (every ~11 minutes)
#
rtcsync
#——————————————————————————
# Leap second handling
#——————————————————————————
# Obtain leap second and TAI-UTC offset information from the system
# timezone database, operating in strict UTC mode.
#
leapsectz right/UTC
view raw gistfile1.txt hosted with ❤ by GitHub

Explanation of Chrony Config for GPSD

OptionPurpose
SOCK /var/run/gpsd.sockRead time from gpsd
refid GPSLabel in chrony output
poll 4Poll every 16 seconds
precision 1e-1~100 ms accuracy (realistic for NMEA)
delay 0.2USB + NMEA latency
makestep 1.0 3Allow initial step corrections

Start and Enable

# systemctl enable chrony.service
# systemctl start chrony.service

After a few minutes, we should start to see pre-configured clients appear in the output of the command below

 # chronyc clients
Hostname                      NTP   Drop Int IntL Last     Cmd   Drop Int  Last
=============================================================================
scar.lab                        5      0   6   -    30       0      0   -     -

Chrony Client-side Setup

Ubuntu Client

$ sudo apt install chrony -y

Configure the to listen to the chrony server address 10.1.10.10. Comment out any other server or pool directives in /etc/chrony/chrony.conf.

server 10.1.10.11

Start and enable the service.

$ sudo systemctl enable chrony --now

Check configured time source for chrony

$ chronyc -n sources -v

What the above output tells you

  • All NTP servers/pools configured for this client
  • Which source is currently selected
  • Reachability and time quality

Key indicators

SymbolMeaning
^*Current sync source
^+Candidate source
^-Reachable, not selected
^?Unusable / not trusted yet

Cisco Catalyst Client Config

Use the commands below to configure your Cisco switch to use the new timesource

# Enter global configuration mode
configure terminal

# Define the NTP server
ntp server 10.1.10.11

# (Optional) Set the switch to use its own hardware clock if it loses sync
ntp master 10

# Exit and save
end
write memory

Leave a Reply