This comprehensive guide covers every aspect from initial FreeBSD installation through final verification, with detailed explanations for each command and troubleshooting steps. The configuration uses the base FreeBSD NTP daemon which includes full GPS and PPS support in FreeBSD 14+, now condensed to 9 essential steps.
Prerequisites and initial setup
Before beginning the detailed configuration, ensure you have a fresh minimal FreeBSD 14+ installation with root SSH access and GPS hardware properly connected:
- Garmin GPS 18x LVC on COM1 (primary)
- u-blox LEA-M8T on COM4 (optional secondary)
Command:
Code: Select all
freebsd-version && uname -a
Code: Select all
# Expected output should show version 14.x or higher
FreeBSD 14.1-RELEASE FreeBSD 14.1-RELEASE...
Why needed: FreeBSD 14+ includes enhanced UART drivers with improved PPS capture capabilities and updated security features that earlier versions lack.
Without this: Older FreeBSD versions may have timing drift issues, missing PPS API features, or incompatible serial port handling that degrades NTP accuracy.
Step 2: Verify base NTP is available
Command:
Code: Select all
which ntpd && ntpd --version
ls -la /etc/ntp.conf
Code: Select all
# Expected output:
/usr/sbin/ntpd
ntpd 4.2.8p15-a (1)
Why needed: Confirms the base NTP daemon is available with the necessary reference clock drivers for GPS operation.
Without this: You need to ensure the base system includes NTP support, which should be present in a standard FreeBSD 14+ installation.
Hardware and serial port configuration
Step 3: Verify GPS hardware detection
Command:
Code: Select all
dmesg | grep -E "(uart|serial)" && ls -la /dev/cua*
Code: Select all
# Expected output:
uart0: <16550 or compatible> port 0x3f8-0x3ff irq 4 flags 0x10 on acpi0
crw-rw---- 1 uucp dialer 0,92 Jan 19 10:30 /dev/cuau0
Why needed: Without proper UART detection, the GPS cannot communicate with the system, making time synchronization impossible.
Without this: The GPS hardware would be inaccessible, and NTP driver 20 would fail to initialize, preventing Stratum 1 operation.
Step 4: Create GPS device symlink
Command:
Code: Select all
# For Garmin GPS 18x LVC on COM1
ln -sf /dev/cuau0 /dev/gps0
echo 'link cuau0 gps0' >> /etc/devfs.conf
# For u-blox LEA-M8T on COM4
ln -sf /dev/cuau3 /dev/gps3
echo 'link cuau3 gps3' >> /etc/devfs.conf
Code: Select all
ls -la /dev/gps*
# Expected output:
# lrwxr-xr-x 1 root wheel 9 Jan 19 10:35 /dev/gps0 -> /dev/cuau0
# lrwxr-xr-x 1 root wheel 9 Jan 19 10:35 /dev/gps3 -> /dev/cuau3
Why needed: NTP driver 20 looks for /dev/gpsN where N is the unit number. With two GPS units configured (127.127.20.0 and 127.127.20.3), we need both symlinks.
Without this: NTP would fail to initialize the GPS reference clocks, logging "GPS_NMEA: cannot open GPS device" errors.
GPS device configuration and testing
Step 5: Test GPS communication with timeout
Command:
Code: Select all
# Test Garmin GPS on COM1
echo "Testing Garmin GPS 18x LVC on COM1:"
timeout 5 cu -l /dev/cuau0 -s 4800
timeout 5 cu -l /dev/gps0 -s 4800
# Test u-blox GPS on COM4
echo "Testing u-blox LEA-M8T on COM4:"
timeout 5 cu -l /dev/cuau3 -s 4800
timeout 5 cu -l /dev/gps3 -s 4800
Code: Select all
# Expected output (example):
Connected
$GPRMC,123456.00,A,40yy.xxxx,N,07yyy.xxxx,W,0.0,0.0,190125,0.0,W*CS
$GPGGA,123456.00,40yy.xxxx,N,07yyy.xxxx,W,1,08,1.2,45.6,M,46.8,M,,*CS
# Exit with ~. (tilde dot) or wait for timeout
Why needed: Confirms both GPS receivers are outputting valid NMEA data and that all device paths are properly configured.
Without this: You would proceed with NTP configuration without knowing if either GPS is functional, leading to debugging difficulties later.
NTP server configuration
Step 6: Create comprehensive ntp.conf configuration
Command:
Code: Select all
cat > /etc/ntp.conf << 'EOF'
# Stratum 1 NTP Server Configuration with GPS 18x LVC
# Panic threshold - 0 allows any time correction
tinker panic 0
# Orphan mode stratum and maximum candidate NTP peers
# See https://www.ntp.org/documentation/4.2.8-series/orphan/
tos orphan 12 maxclock 10
# GPS NMEA driver (driver 20) with PPS
# See https://www.ntp.org/documentation/4.2.8-series/assoc/
# See https://www.ntp.org/documentation/drivers/driver20/
# Mode 1: Process $GPRMC sentences only
# maxpoll 4 for PPS drivers (16s interval)
# Unit 0 - Garmin GPS 18x LVC (127.127.20.0)
# See: https://support.ntp.org/Support/ConfiguringNMEARefclocks#Garmin_GPS18x_LVC
server 127.127.20.0 mode 1 minpoll 3 maxpoll 4
fudge 127.127.20.0 time2 0.600 flag1 1 flag2 0 flag3 1
# Unit 3 - u-blox LEA-M8T (127.127.20.3)
# See: https://support.ntp.org/Support/ConfiguringNMEARefclocks#A_181BLOX_NEO_45M8T
# flag2 1 corrects PPS polarity when using 3.3V TTL-to-RS232 converters
server 127.127.20.3 mode 1 minpoll 3 maxpoll 4
fudge 127.127.20.3 time2 0.200 flag1 1 flag2 1 flag3 1
# Local peer for redundancy (IPv4 only)
# See https://www.ntp.org/documentation/4.2.8-series/assoc/
# For typical Internet/WAN paths: Allan intercept ~2048s → poll 6 (64s)
# For fast LANs with modern computers: Allan intercept ~512s → poll 4 (16s)
#
# See https://www.ntp.org/documentation/4.2.8-series/discipline/
# For symmetric mode peers set minpoll and maxpoll to the same value
#
# LAN peer example (fast local network):
peer -4 192.168.11.1 minpoll 4 maxpoll 4
# NIST time servers (IPv4 only)
# Full list available at: https://tf.nist.gov/tf-cgi/servers.cgi
server -4 utcnist3.colorado.edu iburst
server -4 time-b-g.nist.gov iburst
server -4 time-d-g.nist.gov iburst
server -4 time-a-wwv.nist.gov iburst
server -4 time-c-wwv.nist.gov iburst
server -4 time-e-wwv.nist.gov iburst
server -4 time-a-b.nist.gov iburst
server -4 time-c-b.nist.gov iburst
server -4 time-e-b.nist.gov iburst
# Enable statistics
enable stats
# Enable automatic calibration (disable after determining time2 values)
# See addendum for calibration procedure
#enable calibrate
# Statistics configuration - see https://www.ntp.org/ntpfaq/ntp-s-trouble/
# Section 8.1.2 explains peerstats and loopstats file formats:
# loopstats: day, second, offset, drift compensation, estimated error, stability, polling interval
# peerstats: day, second, address, status, offset, delay, dispersion, skew (variance)
statistics clockstats loopstats peerstats
statsdir /var/log/ntp
# Logging configuration - see https://www.ntp.org/ntpfaq/ntp-s-config/
# Section 6.1.4.1 for detailed logging options
# Current setting logs all sync, clock, peer, and system events
logconfig =syncall +clockall +peerall +sysall
# For debugging, you could use: logconfig =all
# Drift file location
driftfile /var/db/ntpd.drift
# Access control
restrict default kod limited nomodify notrap
restrict -6 default kod limited nomodify notrap
restrict 192.168.11.0 mask 255.255.255.0
# Leap seconds file
leapfile /var/db/ntpd.leap-seconds.list
EOF
Code: Select all
# Create statistics directory if it doesn't exist
mkdir -p /var/log/ntp
chown root:wheel /var/log/ntp
# Test configuration syntax by checking if ntpd can parse it
ntpd -n -c /etc/ntp.conf -f /dev/null 2>&1 | head -20
# Look for any error messages in the output
Why needed: This configuration provides better GPS synchronization, improved logging for troubleshooting, and prevents startup failures due to large time corrections.
Without this: The system might experience high root dispersion, IPv6 connectivity issues, or refuse to synchronize if the initial time offset is too large.
Step 7: Configure leap seconds file
Command:
Code: Select all
# Enable automatic leap second updates
echo 'daily_ntpd_leapfile_enable="YES"' >> /etc/periodic.conf
# Download the current leap seconds file
/etc/periodic/daily/480.leapfile-ntpd
# Add leapfile directive to ntp.conf (already included in step 6)
# Verify it's in the configuration
grep leapfile /etc/ntp.conf
Code: Select all
head -5 /var/db/ntpd.leap-seconds.list
# Should show comments with expiration date
Why needed: Leap seconds are occasionally added to UTC to keep it synchronized with Earth's rotation. Without this file, ntpd cannot properly handle leap second events.
Without this: The server would not properly handle leap seconds, potentially causing a one-second error during leap second events.
Step 8: Configure NTP service startup
Command:
Code: Select all
sysrc ntpd_enable="YES"
sysrc ntpd_sync_on_start="YES"
Code: Select all
service ntpd start
service ntpd status
# Expected output: ntpd is running as pid xxxxx
Why needed: These settings ensure ntpd starts at boot. The ntpd_sync_on_start allows initial time synchronization even with large offsets.
Without this: The system would not start ntpd automatically at boot.
Service startup and verification
Step 9: Verify GPS reference clock initialization
Command:
Code: Select all
ntpq -p
ntpq -c assoc
Code: Select all
# Expected output from ntpq -p:
remote refid st t when poll reach delay offset jitter
=============================================================================
*GPS_NMEA(3) .GPS. 0 l 4 16 377 0.000 0.001 0.002
+192.168.11.1 10.1.1.1 2 s 64 512 377 0.123 -1.234 2.345
# Expected output from ntpq -c assoc:
ind assid status conf reach auth condition last_event cnt
===========================================================
1 22429 971a yes yes none pps.peer sys_peer 1
2 22430 8011 yes no none reject mobilize 1
# Line 1 with "pps.peer" and "sys_peer" indicates GPS with PPS is the system peer
Why needed: This confirms the GPS reference clock is functioning with PPS timing and being used as the primary time source.
Without this: You wouldn't know if the GPS timing is working properly, potentially operating with degraded accuracy or no GPS synchronization at all.
Performance validation and monitoring
Step 10: Final comprehensive status verification
Command:
Code: Select all
echo "=== NTP Status Summary ==="
date
ntpq -p
echo -e "\n=== GPS Reference Clock Details ==="
ntpq -c assoc | grep "pps.peer"
echo -e "\n=== System Variables ==="
ntpq -c "rv" | grep -E "(stratum|precision|rootdelay|rootdisp)"
echo -e "\n=== Recent GPS Statistics ==="
tail -5 /var/db/ntp/clockstats 2>/dev/null || echo "Statistics not yet available"
Code: Select all
# Expected output showing:
# - Current time in sync
# - GPS marked with 'o' (PPS discipline)
# - Stratum 1 operation
# - Low jitter values (<1ms)
# - Recent clockstats entries showing GPS NMEA sentences
# The 'o' before GPS_NMEA(3) indicates PPS discipline is active
# NIST servers marked with + are good candidates, - are excluded
# clockstats show valid GPS fixes (A in GPRMC sentences)
Why needed: This final verification ensures all components are working correctly and the server is ready for production use as an authoritative time source.
Without this: Subtle configuration issues or performance problems might go undetected, leading to unreliable time service or degraded accuracy over time.
Conclusion and ongoing monitoring
Your FreeBSD 14+ system is now configured as a precision Stratum 1 NTP server using GPS receivers with PPS timing. The configuration supports dual GPS units (Garmin GPS 18x LVC and u-blox LEA-M8T) for redundancy and provides microsecond-level accuracy through hardware PPS signals while maintaining fallback connectivity to NIST time servers.
Key monitoring commands for ongoing maintenance:
- - Check peer status and synchronization (look for 'o' prefix on GPS)
Code: Select all
ntpq -p
- - Verify stratum level and timing accuracy
Code: Select all
ntpq -c sysinfo
- - Monitor GPS NMEA sentences
Code: Select all
tail /var/log/ntp/clockstats
- - Confirm service health
Code: Select all
service ntpd status
- Timing accuracy: <1 millisecond (typically <100 microseconds with PPS)
- Stratum level: 1 (primary reference)
- GPS lock indicator: 'A' in GPRMC sentences
- PPS indicator: 'o' prefix in ntpq output for primary GPS
- Clock jitter: <0.1 milliseconds
- Multiple GPS: Both units may show as available peers if configured
Addendum: Calibrating GPS time2 Offset Values
The
Code: Select all
fudge time2
Two calibration methods are available:
- Automatic calibration using - Best when you have reliable internet servers
Code: Select all
enable calibrate
- Manual calibration using noselect - Best for isolated networks or when internet servers are unreliable
If you need to fine-tune the
Code: Select all
fudge time2
Step 1: Create calibration configuration
Create a temporary ntp.conf for calibration:
Code: Select all
cp /etc/ntp.conf /etc/ntp.conf.calibration
vi /etc/ntp.conf
Code: Select all
# GPS NMEA driver - calibration mode (no PPS, noselect)
server 127.127.20.0 mode 1 minpoll 4 maxpoll 4 noselect
fudge 127.127.20.0 time2 0.000 flag1 0 flag3 1
- Add to prevent GPS from being selected as primary source
Code: Select all
noselect
- Set to disable PPS
Code: Select all
flag1 0
- Set for baseline measurement
Code: Select all
time2 0.000
Code: Select all
service ntpd restart
# Wait 30-60 minutes for stability
sleep 3600
Code: Select all
ntpq -p | grep GPS_NMEA
# Note the offset value (in milliseconds)
Code: Select all
GPS_NMEA(0) .GPS. 0 l 16 16 377 0.000 -450.123 0.432
The fudge time2 value should be the negated offset in seconds:
- If offset shows -450.123 ms, use time2 0.450
- If offset shows +320.456 ms, use time2 -0.320
Code: Select all
vi /etc/ntp.conf
Code: Select all
# GPS NMEA driver with calibrated time2
server 127.127.20.0 mode 1 minpoll 4 maxpoll 4
fudge 127.127.20.0 time2 0.450 flag1 1 flag3 1
Code: Select all
service ntpd restart
After 10-15 minutes, verify the GPS offset is now close to zero:
Code: Select all
ntpq -p | grep GPS_NMEA
# Offset should be within ±10ms
Alternative: Automatic Calibration Using enable calibrate
NTP provides an automatic calibration feature that can calculate the time2 offset for you. See https://www.ntp.org/documentation/4.2.8-series/refclock/
Step 1: Create automatic calibration configuration
Code: Select all
cp /etc/ntp.conf /etc/ntp.conf.autocalibration
vi /etc/ntp.conf
Code: Select all
# Enable automatic calibration
enable calibrate
# GPS NMEA drivers with no initial time correction
server 127.127.20.0 mode 1 minpoll 4 maxpoll 4
fudge 127.127.20.0 time2 0.000 flag1 1 flag2 0 flag3 1
server 127.127.20.3 mode 1 minpoll 4 maxpoll 4
fudge 127.127.20.3 time2 0.000 flag1 1 flag2 1 flag3 1
# Select one highly reliable server as preferred reference
server time.nist.gov iburst prefer
# Additional servers for validation
server 0.pool.ntp.org iburst
server 1.pool.ntp.org iburst
Code: Select all
service ntpd restart
# Let it run for 1-2 hours for accurate results
Code: Select all
# Look for calibration messages in logs
grep "refclock_report" /var/log/messages
# Example output:
# ntpd[1234]: refclock_report: 127.127.20.0: time2 0.598
# ntpd[1234]: refclock_report: 127.127.20.3: time2 0.203
Update your ntp.conf with the reported time2 values and remove the calibration:
Code: Select all
vi /etc/ntp.conf
Code: Select all
enable calibrate
Code: Select all
fudge 127.127.20.0 time2 0.598 flag1 1 flag2 0 flag3 1
fudge 127.127.20.3 time2 0.203 flag1 1 flag2 1 flag3 1
Code: Select all
service ntpd restart
Hardware-Specific Notes
u-blox GPS with TTL-to-RS232 Converters:
If using a u-blox GPS receiver (like NEO-6M/7M/8M or LEA-M8T) with a 3.3V TTL-to-RS232 converter, the converter inverts the PPS signal logic. The falling edge of DCD becomes the active edge instead of the rising edge. To correct this, add
Code: Select all
flag2 1
Code: Select all
fudge 127.127.20.3 time2 0.200 flag1 1 flag2 1 flag3 1
Calibration Summary
Both calibration methods achieve the same goal: determining the correct time2 offset for your GPS hardware. Choose the method that best fits your setup:
- Automatic calibration: Easier, requires a trusted preferred peer, good for initial setup
- Manual calibration: More control, works without internet servers, better for isolated networks