Deploy two internal NTP servers
Point all switches and PLCs to internal stratum 2 servers. Two servers provide redundancy if one fails.
Accurate DNS records depend on accurate time. If a device’s clock drifts, log timestamps become unreliable and certificate validation fails. NTP solves this.
NTP (Network Time Protocol) synchronizes clocks across the network using a stratum hierarchy. Stratum 0 is a reference clock (GPS receiver, atomic clock). Stratum 1 is a server directly connected to a stratum 0 source. Each additional hop adds one stratum level.
Stratum 16 means the server is unsynchronized and its time is unreliable. NTP clients reject stratum 16 sources. In a plant network, deploy at least two stratum 2 servers (synchronized to public stratum 1 servers or a local GPS receiver) and point all switches and PLCs to those internal servers.
NTP achieves millisecond-level accuracy over a WAN and sub-millisecond on a LAN. For most OT applications (logging, certificate validation, SCADA timestamps), NTP is sufficient.
PTP (Precision Time Protocol, IEEE 1588) achieves sub-microsecond accuracy by using hardware timestamping in network interface cards and switches. PROFINET IRT requires PTP because its cycle times (250 microseconds to 1 millisecond) demand clock synchronization tighter than NTP provides. IEC 61850 Sampled Values for power grid protection also requires PTP.
| Feature | NTP | PTP (IEEE 1588) |
|---|---|---|
| Accuracy | 1 to 10 ms (LAN) | < 1 microsecond |
| Hardware support | None required | Requires PTP-capable NICs and switches |
| Use case | Logging, certificates, SCADA | PROFINET IRT, IEC 61850, motion control |
| Standard | RFC 5905 | IEEE 1588-2019 |
The following script queries an NTP server and reports the offset between the local clock and the server, which helps identify devices with clock drift.
import ntplibfrom datetime import datetime, timezone
def check_ntp(server: str) -> dict: client = ntplib.NTPClient() response = client.request(server, version=3) return { "server": server, "stratum": response.stratum, "offset_ms": response.offset * 1000, "delay_ms": response.delay * 1000, "ref_time": datetime.fromtimestamp(response.ref_time, tz=timezone.utc), }
for server in ["192.168.1.1", "pool.ntp.org"]: try: stats = check_ntp(server) print(f"Server: {stats['server']} Stratum: {stats['stratum']} " f"Offset: {stats['offset_ms']:.3f} ms " f"Delay: {stats['delay_ms']:.3f} ms") if abs(stats["offset_ms"]) > 100: print(f" WARNING: offset > 100 ms, check NTP configuration") except ntplib.NTPException as e: print(f"Server: {server} ERROR: {e}")An offset greater than 100 ms indicates a configuration problem. An offset greater than 1 second causes syslog timestamps to be unreliable for incident correlation.
Deploy two internal NTP servers
Point all switches and PLCs to internal stratum 2 servers. Two servers provide redundancy if one fails.
NTP for logging, PTP for real-time
NTP provides millisecond accuracy for logs and certificates. PTP provides sub-microsecond accuracy for PROFINET IRT and IEC 61850.
Stratum 16 means unsynchronized
NTP clients reject stratum 16 sources. If a device reports stratum 16, its time is unreliable.
With addresses assigned, names resolved, and clocks synchronized, the remaining question is how to monitor the health of the network itself. The next page covers SNMP, the protocol that polls device statistics and receives event notifications.