Configuring NTP and PTP Sync on an Industrial Edge Gateway
The best drift-correction model is the one you never have to run: if the edge gateway’s own clock is disciplined to a trustworthy reference before it timestamps a single sample, the offset-and-slope regression under clock drift correction has almost nothing left to fix. This recipe configures a Linux edge gateway with two complementary services — chrony for Network Time Protocol (NTP) and linuxptp for Precision Time Protocol (PTP, IEEE 1588) — so it holds sub-millisecond offset under NTP alone, and sub-microsecond offset once a PTP grandmaster and PTP-aware NIC are available. Every command below is what you would actually run on a Debian- or Yocto-based gateway sitting between a PTP-capable managed switch and a fleet of PLCs.
1. Configure chrony against the plant NTP reference Permalink to this section
Start with NTP even on gateways that will eventually run PTP: chrony steps the clock into rough alignment before PTP has a lock, and stays available as the holdover fallback in step 4. Configure chronyd against the plant’s internal NTP stratum rather than a public pool, since the OT network is usually air-gapped.
sudo apt-get install -y chrony
sudo systemctl disable --now systemd-timesyncd
# /etc/chrony/chrony.conf on the edge gateway
pool time.plant.internal iburst maxsources 4
server ptp-grandmaster.plant.internal iburst prefer minpoll 4 maxpoll 6
driftfile /var/lib/chrony/chrony.drift
rtcsync
makestep 1.0 3
maxupdateskew 5.0
leapsectz right/UTC
# Fallback reference so the gateway does not free-run if every server is lost
local stratum 10 orphan
allow 10.20.0.0/16
cmdport 323
logdir /var/log/chrony
log tracking measurements statistics
makestep 1.0 3 lets chrony step (rather than slew) the clock only for the first three updates — after that it must slew smoothly, since a mid-shift step would violate the monotonicity contract clock drift correction depends on downstream. prefer keeps chrony from wandering to a lower-quality pool member when both are reachable. Restart and confirm a source is selected:
sudo systemctl restart chrony
chronyc sources -v
2. Bring up PTP with ptp4l and phc2sys Permalink to this section
PTP hardware timestamping is what takes the gateway from millisecond-class NTP offset to sub-microsecond offset. Install linuxptp and configure ptp4l as a slave-capable client on the NIC facing the PTP-aware switch.
sudo apt-get install -y linuxptp
# /etc/linuxptp/ptp4l.conf
[global]
domainNumber 0
priority1 128
priority2 128
slaveOnly 0
gmCapable 0
time_stamping hardware
network_transport UDPv4
delay_mechanism E2E
step_threshold 0.00002
tx_timestamp_timeout 10
summary_interval 4
logging_level 6
[eth0]
gmCapable 0 and slaveOnly 0 let the Best Master Clock Algorithm (BMCA) decide the role dynamically rather than hard-pinning the gateway as a slave, useful if the unit later doubles as a boundary clock for downstream PLCs. delay_mechanism must match the upstream switch — mixing End-to-End (E2E) with Peer-to-Peer (P2P) on the same segment prevents sync entirely rather than degrading it. Start ptp4l, then chain phc2sys to step CLOCK_REALTIME to the NIC’s hardware clock once ptp4l reports a lock:
sudo ptp4l -f /etc/linuxptp/ptp4l.conf -i eth0 -m -S &
sudo phc2sys -s eth0 -c CLOCK_REALTIME -w -m -O -37 &
The -O -37 flag is not optional and is the single most common linuxptp misconfiguration: PTP’s default profile ticks in International Atomic Time (TAI), not UTC, and TAI has run 37 seconds ahead of UTC since the last leap second in January 2017. Without it, CLOCK_REALTIME ends up 37 seconds fast against every other UTC-based system on the line. -w makes phc2sys wait for ptp4l’s servo to lock before touching the system clock, avoiding a spurious step at startup. In production, wrap both commands in systemd units with Requires=/After= ordering rather than backgrounding them by hand.
3. Verify hardware timestamping on the gateway NIC Permalink to this section
time_stamping hardware in step 2 is a request, not a guarantee — a driver lacking a PTP hardware clock (PHC) silently falls back to software timestamps, and the offset budget from the diagram above collapses from nanoseconds to tens of microseconds without any error message. Confirm the capability before trusting the deployment:
ethtool -T eth0
Look for hardware-transmit, hardware-receive, and hardware-raw-clock in the SOF_TIMESTAMPING capability list, and a non-negative PTP Hardware Clock index. Confirm the device node exists and resolve which /dev/ptpN belongs to which NIC — on a gateway with several interfaces this is easy to get backwards:
ls /sys/class/net/eth0/device/ptp/
cat /sys/class/net/eth0/device/ptp/ptp0/clock_name
If ethtool -T reports only software-transmit / software-receive, either the NIC silicon lacks a PHC (common on consumer-grade controllers) or the driver needs a newer kernel. Intel I210/I219 or comparable PTP-capable controllers are the safe baseline; do not spec a gateway SKU without checking this line item.
4. Configure holdover and grandmaster failover Permalink to this section
A PTP domain with one grandmaster is a single point of failure for every timestamp on the line. Two mechanisms bound the damage when the grandmaster or its network path disappears. First, run a second candidate grandmaster with a numerically worse priority1 (a higher number loses the BMCA election) so the gateway fails over automatically:
# /etc/linuxptp/ptp4l.conf on the secondary grandmaster candidate
[global]
domainNumber 0
priority1 200
priority2 128
gmCapable 1
time_stamping hardware
network_transport UDPv4
delay_mechanism E2E
[eth0]
Second, bound how long the gateway can coast on its local oscillator before holdover accuracy matters. A TCXO drifts on the order of 0.5–2 ppm and an OCXO on the order of 0.01 ppm or better; at 0.01 ppm an OCXO-equipped gateway stays within 1 µs of true time for roughly 100 seconds of holdover, and within 1 ms for nearly 28 hours — pick the oscillator grade against how long a grandmaster outage is tolerable before piecewise-linear correction for thermal oscillator drift has to take over. A small watchdog keeps the failure visible instead of silent:
#!/usr/bin/env bash
# ptp-holdover-check.sh — alert if the PHC has lost lock
set -euo pipefail
state=$(pmc -u -b 0 'GET PORT_DATA_SET' 2>/dev/null | awk '/portState/{print $2}')
if [[ "${state}" != "SLAVE" && "${state}" != "MASTER" ]]; then
logger -t ptp-holdover "port state ${state:-UNKNOWN}"
exit 1
fi
5. Verify offset with chronyc and pmc Permalink to this section
Close the loop by reading the offset each service reports, not just its process status. For chrony:
chronyc tracking
Confirm System time is on the order of microseconds to low milliseconds and Leap status reads Normal. For PTP, pmc (the PTP management client shipped with linuxptp) queries ptp4l directly:
pmc -u -b 0 'GET TIME_STATUS_NP'
pmc -u -b 0 'GET CURRENT_DATA_SET'
TIME_STATUS_NP reports master_offset in nanoseconds — a healthy hardware-timestamped slave should sit within a few hundred nanoseconds. CURRENT_DATA_SET reports stepsRemoved (hop count to the grandmaster) and offsetFromMaster; stepsRemoved climbing over time signals a flapping BMCA election, worth graphing alongside drift metrics from the correction stage itself. Finally, cross-check the PHC against the system clock directly:
phc_ctl /dev/ptp0 cmp
If both agree with the offset budget in the diagram above, the gateway’s raw timestamps need little more than the residual affine correction that clock drift correction applies as a safety net — not a rescue from a clock never disciplined at all.
Gotchas & anti-patterns Permalink to this section
- Running
systemd-timesyncdandchronydtogether. Both claim the NTP client role and fight over the clock, producing an oscillating offset that looks like a hardware fault. - Trusting
time_stamping hardwarewithout checkingethtool -T. A driver without a PHC silently timestamps in software, and the gateway reports a confident sub-microsecond offset that is actually off by tens of microseconds. - Mismatched
delay_mechanismacross a boundary clock hop.E2Eon one side andP2Pon the other does not degrade gracefully — the slave never locks, and the symptom looks like a routing problem. - Forgetting the
-O -37TAI–UTC offset onphc2sys. The gateway reports rock-solid PTP lock while every UTC-based log line and MQTT payload timestamp runs 37 seconds fast. - Sizing holdover for the average outage, not the worst case. A TCXO-grade gateway that only saw a two-minute blip in testing can drift milliseconds during a real overnight maintenance window; size the oscillator to the outage SLA, not the demo.
Quick reference Permalink to this section
| Sync method | Typical accuracy | Hardware requirement | Relative cost |
|---|---|---|---|
| NTP, software timestamps (chrony default) | 1–10 ms | none | low |
| NTP, hardware timestamps | 100 µs – 1 ms | NIC with hardware RX/TX timestamping | low–medium |
| PTP, software timestamps | 10–100 µs | none beyond a standard NIC | medium |
| PTP, hardware timestamps (E2E, one-step) | 0.1–1 µs | PTP-capable NIC with a PHC (/dev/ptp0) |
medium–high |
| PTP with boundary clocks / GPS grandmaster | tens of ns – 100 ns | dedicated PTP-aware switches, GNSS-fed grandmaster | high |
Related Permalink to this section
- Clock Drift Correction — the correction layer this configuration is meant to leave with almost nothing to do
- Piecewise-Linear Correction for Thermal Oscillator Drift — what to do when holdover alone cannot bound thermal drift
- Syncing Edge Timestamps with NTP Servers — the NTP-only baseline this recipe extends with PTP
- Correcting Timezone Shifts Across Global Plants — keeping a disciplined UTC clock from being misread across sites