← All articles
service 24 hours neon sigange

Running an NTP Time Server in Your Homelab

Networking 2026-03-04 · 4 min read ntp time server chrony homelab raspberry-pi gps stratum networking
By HomeLab Starter Editorial TeamHome lab enthusiasts covering hardware setup, networking, and self-hosted services for home and small office environments.

Accurate time synchronization matters more than it seems. Certificate validation, log correlation, Kerberos authentication, and replication protocols all depend on accurate clocks. A homelab NTP server centralizes time sync, reduces external NTP queries, and opens the door to a Stratum 1 setup using GPS.

Photo by Eric Tompkins on Unsplash

Why Run a Local NTP Server

Stratum Levels

NTP uses a stratum hierarchy:

Stratum Description
0 Reference clock (GPS, atomic) — not directly networked
1 Server connected directly to a Stratum 0 device
2 Server syncing from a Stratum 1
3 Server syncing from a Stratum 2

Public NTP pools (pool.ntp.org) are typically Stratum 2-3. A GPS-disciplined Raspberry Pi gives you Stratum 1 on your LAN.

Basic Setup: chrony as an NTP Server

Chrony is the recommended NTP implementation on modern Linux — it's more accurate than ntpd and handles network disruptions better.

Install and Configure

# Debian/Ubuntu
sudo apt install chrony

# RHEL/Fedora
sudo dnf install chrony

Edit /etc/chrony.conf:

# Upstream NTP sources (4 for resilience)
pool 0.pool.ntp.org iburst
pool 1.pool.ntp.org iburst
pool 2.pool.ntp.org iburst
pool 3.pool.ntp.org iburst

# Allow your LAN to query this server
allow 192.168.1.0/24

# Serve time even if not fully synced (useful for isolated networks)
local stratum 10

# Log directory
logdir /var/log/chrony

# Good defaults
makestep 1.0 3
rtcsync
driftfile /var/lib/chrony/drift
sudo systemctl enable --now chronyd

Verify It's Working

# Check sources
chronyc sources -v

# Check tracking stats
chronyc tracking

# See if it's serving
chronyc clients

Like what you're reading? Subscribe to HomeLab Starter — free weekly guides in your inbox.

Configuring Clients to Use Your Server

Linux clients (chrony)

# /etc/chrony.conf on client
server 192.168.1.10 iburst prefer

Linux clients (systemd-timesyncd)

# /etc/systemd/timesyncd.conf
[Time]
NTP=192.168.1.10
FallbackNTP=pool.ntp.org
sudo systemctl restart systemd-timesyncd
timedatectl timesync-status

Router-distributed NTP (recommended)

Configure your DHCP server (OPNsense, pfSense, Dnsmasq) to push your NTP server address via DHCP option 42. All DHCP clients automatically receive and use your server without individual configuration.

In OPNsense: Services → DHCPv4 → [Interface] → NTP servers → enter your server IP.

In dnsmasq: dhcp-option=42,192.168.1.10

Stratum 1 with GPS

For sub-millisecond accuracy, connect a GPS module to a Raspberry Pi and use the GPS pulse-per-second (PPS) signal as a hardware reference.

Hardware

Software: gpsd + chrony with PPS

sudo apt install gpsd gpsd-clients pps-tools

Edit /etc/default/gpsd:

DEVICES="/dev/ttyAMA0 /dev/pps0"
GPSD_OPTIONS="-n"

Verify GPS lock:

cgps -s        # Shows satellite info
ppstest /dev/pps0  # Confirms PPS signal

Configure chrony with PPS:

# /etc/chrony.conf on the Pi
# GPS Serial data (lower precision, but provides time of day)
refclock SHM 0 offset 0.5 delay 0.2 refid GPS

# GPS PPS signal (high precision)
refclock PPS /dev/pps0 lock GPS refid PPS

# Upstream peers as fallback
pool pool.ntp.org iburst

# Serve to LAN
allow 192.168.1.0/24
local stratum 1

After GPS acquires a fix (5-15 minutes), chronyc sources shows the PPS source at Stratum 0 and your server becomes Stratum 1.

Accuracy Results

A Raspberry Pi Stratum 1 with GPS PPS typically achieves:

Compare this to public pool.ntp.org: 1-10 milliseconds.

Monitoring Time Sync

chronyc commands

chronyc sources           # List time sources and their offsets
chronyc sourcestats       # Statistical data for each source
chronyc tracking          # Current sync status
chronyc clients           # Who's querying your server
chronyc activity          # Active sources count

ntpdate check (quick one-off)

ntpdate -q 192.168.1.10   # Query without adjusting

Prometheus metrics

Chrony exposes metrics via a Prometheus exporter:

chrony_exporter --listen-address=:9123

Add to your Prometheus scrape config and visualize in Grafana.

Firewall Rules

NTP uses UDP port 123. Allow inbound from your LAN:

# iptables
iptables -A INPUT -p udp --dport 123 -s 192.168.1.0/24 -j ACCEPT

# ufw
ufw allow from 192.168.1.0/24 to any port 123 proto udp

Common Issues

Chrony not syncing: Check chronyc sources — if offset is large, chrony waits for it to converge. makestep 1.0 3 in the config forces a step correction during the first 3 updates if offset exceeds 1 second.

"No valid sources" error: Verify upstream NTP is reachable, firewall allows outbound UDP 123.

Clients not using your server: Confirm allow directive includes the client subnet. Test with chronyc clients after a few minutes.

GPS not locking: Check antenna placement (needs sky view), verify UART is enabled on Pi (raspi-config → Interfaces → Serial).

When to Run a Stratum 1

A Stratum 2/3 chrony server (syncing from public NTP) is sufficient for nearly all homelab use cases. The accuracy improvement from Stratum 1 GPS matters if you're:

For most homelabs: install chrony, point at pool.ntp.org, set allow for your LAN, done. Add the GPS module if you want the accuracy and the project.

Get free weekly tips in your inbox. Subscribe to HomeLab Starter