Skip to content

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.

Edge gateway clock chain: GNSS to grandmaster to boundary clock to gateway PHC to timestamp A GNSS-disciplined PTP grandmaster feeds a boundary clock within 100 ns, which feeds the gateway's hardware clock within 250 ns; phc2sys steps the system clock to within 1 microsecond. A dashed branch shows an NTP pool feeding chrony as a holdover fallback bounded by local oscillator drift. GNSS REFERENCE PTP DOMAIN · IEEE 1588 GATEWAY / APPLICATION GNSS antenna GPS 1PPS + 10 MHz PTP grandmaster stratum 1 · priority1=128 ≤100 ns Boundary clock PTP-aware switch ≤250 ns Gateway PHC /dev/ptp0 · ptp4l slave ≤1 µs System clock CLOCK_REALTIME NTP pool chrony · holdover fallback holdover if grandmaster lost

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-timesyncd and chronyd together. Both claim the NTP client role and fight over the clock, producing an oscillating offset that looks like a hardware fault.
  • Trusting time_stamping hardware without checking ethtool -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_mechanism across a boundary clock hop. E2E on one side and P2P on the other does not degrade gracefully — the slave never locks, and the symptom looks like a routing problem.
  • Forgetting the -O -37 TAI–UTC offset on phc2sys. 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