Why NTP's Missing Authentication Became Load-Bearing
NTP has shipped without authentication since 1985, and it is the protocol every certificate, token, and signature check is measured against. The exchange is 48 bytes out, 48 bytes back, and the client believes it — no signature, no identity.
That was tolerable while a few seconds of skew was an operational annoyance. It is not tolerable now. Certificate notBefore and notAfter are compared to the local clock. "Valid for 15 minutes" is a statement about a clock. Rejecting requests older than N seconds requires knowing the time. Two hosts that disagree produce a log timeline that never happened.
The circularity is the hard part: X.509 validity checking needs the current time, so teams either disable cert time validation or reach for a workaround — skipping validation until the clock settles (leaving the device exposed exactly when it is most vulnerable), or building in slack so issuers backdate notBefore and validators widen windows. Both sides guess at the other's error.
Slack worked while certificates lasted 398 days. CA/Browser Forum ballot SC-081v3 cut the cap on newly issued publicly trusted TLS certificates to 200 days in March 2026, 100 days in March 2027, and 47 days in March 2029, with domain validation reuse falling to 10 days at that point. At 47 days, renewal is roughly monthly and unattended, typically over ACME. A renewal loop on a skewed clock does not file a ticket — it reissues or fails on an unwatched schedule.
Meta's public time service now speaks NTS (Network Time Security, RFC 8915) at nts.meta.com, so packets are authenticated and a device can verify the time came from Meta and was not modified in transit. The work builds on the earlier migration from ntpd to chrony, 10 milliseconds to 100 microseconds, and opening time.meta.com to everyone: that made time precise, this makes it verifiable.
Key Establishment, Then Authenticated NTP
NTS separates into two phases, which is what makes it deployable.
Phase 1 — NTS-KE. TLS 1.3 over TCP/4460, negotiated with ALPN ntske/1. The peers agree on an AEAD algorithm and derive two directional session keys (C2S and S2C) from the TLS session via the RFC 5705 exporter. The server issues eight cookies, advertises the NTP server to use in a server negotiation record, and closes. This runs once, not per packet. Three AEADs are negotiated — AES-SIV-CMAC-256 (ID 15), which RFC 8915 makes mandatory to implement and an unknown client is most likely to request; AES-SIV-CMAC-512 (17); and AES-128-GCM-SIV (30) — with client preference winning.
Phase 2 — authenticated NTP. Standard NTPv4 over UDP/123 to the advertised responder, with NTS extension fields. A request carries a unique identifier, a cookie, and an authenticator that covers everything before it but encrypts nothing. The server opens the cookie to recover session keys, verifies the authenticator, and replies with the unique identifier echoed back plus its own authenticator — that one carrying a payload, the fresh cookies, encrypted inside.
Forged packets fail verification and are dropped, and because no NTS NAK is sent, failure is indistinguishable from packet loss and cannot be used to push a client into a new key exchange. Replayed responses carry the wrong unique identifier and match no outstanding request. Replacement cookies travel inside the response authenticator rather than in the clear, so an observer sees a different opaque blob each exchange and cannot track a client across networks. Single-use cookies are the client's half of the bargain; the server tracks nothing.
Cookies Without a Key Ring
A cookie is server state handed to the client for safekeeping: the session keys, sealed with AES-SIV so only the server can open them. The obvious approach — a key ring replicated to every server — turns time service into a distributed state problem. Meta does not replicate keys. Each server derives the sealing key from a shared master secret and the current day.
| key = HKDF-SHA256(master, salt = BE32(day), info = “fbnts-cookie-seal-v1”) |
Since the Unix epoch, day counts whole 24-hour periods, not calendar days, so there is no timezone and no DST and every host computes the same integer for the same instant. That integer is the cookie's key ID and travels in the clear. A server handed a cookie it has never seen, sealed by a host it has never spoken to, re-derives the key and opens it. Two days back and one day forward are accepted to absorb skew around a rotation boundary.
What falls away is the key ring, replication, the session table, shared state that can drift, and anything an attacker can exhaust. It is also what makes the phase split work: the KE server and the NTP responders it hands clients off to are different machines in different places that never exchange anything. Adding a server means adding a server.
The plain NTP path is unchanged. Extension fields are parsed only when present, so unauthenticated clients hit the same code at the same cost.
Configuring a Client
One line of chrony config gets you started.
| pool nts.meta.com nts iburst maxsources 5 |
nts.meta.com terminates key exchange only; the responder is advertised in a server negotiation record, so the client is steered to time.meta.com and sends authenticated NTP there. The host you configure is not the host you talk to.
That indirection is why the example uses pool rather than server. A single server line yields one association and one responder — a single point of failure — and NTP wants several sources so it can outvote a bad one. pool requests maxsources independent associations, each running its own key exchange over its own TLS session and ending up with its own pair of session keys.
| Name/IP address | Mode | KeyID | Type | KLen | Last | Atmp | NAK | Cook | CLen |
|---|---|---|---|---|---|---|---|---|---|
| time1.meta.com | NTS | 5 | 30 | 128 | 27 | 0 | 0 | 8 | 68 |
| time2.meta.com | NTS | 7 | 30 | 128 | 142 | 0 | 0 | 8 | 68 |
| time3.meta.com | NTS | 10 | 30 | 128 | 548 | 0 | 0 | 8 | 68 |
| time4.meta.com | NTS | 1 | 30 | 128 | 1177 | 0 | 0 | 8 | 68 |
| time5.meta.com | NTS | 2 | 30 | 128 | 1177 | 0 | 0 | 8 | 68 |
Five rows, five independent authenticated sources, from one line of config and one KE endpoint. Reading across: Type 30 is the negotiated AEAD, AES-128-GCM-SIV, with KLen 128 bits of session key (chrony lists it first, and client preference wins); CLen 68 is the cookie, 36 bytes of envelope around the two 16-byte keys inside; Cook 8 is cookies in hand; NAK 0 means none has been rejected.
The more interesting half isn't in the table. Each row's session keys came from its own TLS handshake and work only for that association, while the key that seals them into a cookie is the shared one — the epoch day number, identical on every responder, stamped inside each cookie and never visible to the client. That split is what lets one key exchange hand a client five responders that have never spoken to each other. As a pool member completes key exchange and moves to its negotiated responder, the freed KE address goes to the next unresolved member, repeating until maxsources responders have answered; five sources come up in about 20 minutes.
What Authentication Does and Does Not Cover
NTS stops a man-in-the-middle from forging the answer. A classic MITM does not need to break cryptography — it only has to answer before a real server does. Rolling a clock backward can make expired certificates validate again and revoked tokens work again, resetting every control that depends on something having expired. Rolling a clock forward expires everything at once, and beyond a point that failure stops being recoverable.
Take a device convinced it is the year 2037. NTP's 32-bit seconds field wraps in February 2036, so a client stranded on the far side computes its correction into the wrong era, moving a wrong clock further away. Every certificate it holds is long expired, TLS fails, and it cannot reach anything that might help — including its own update service. The patch that would fix it cannot be delivered because delivery needs the broken clock. Nothing is damaged; every component works as designed. The device just holds a number that puts it out of reach, and no amount of waiting or rebooting helps — recovery is a factory reset or a service center. Across a fleet, a wrong integer becomes a logistics problem.
The same dynamic hits a host that issues credentials. Short lifetimes do the job revocation otherwise would, but only as a function of the issuer's clock. An issuer convinced it is next year signs credentials that outlive the designed window.
Several things remain outside NTS's reach. RFC 8915 is explicit on delay attacks: an attacker who holds packets shifts the clock by up to half the introduced delay, and no cryptography detects it, because every byte is genuine. Authentication says who wrote a packet, not how long it flew. The familiar mitigations apply — multiple independent sources, sanity limits, bounded steps. An on-path attacker can still delay or drop packets; what they can no longer do is lie about the contents.
Bootstrapping is unsolved by the same circularity: NTS-KE runs over TLS, TLS wants a working clock, so a device with a dead RTC still needs to be roughly correct before its first handshake. Wrong servers are authenticated too — NTS secures the path, not the answer — so a correctly configured, correctly signed, simply wrong server hands over bad time, and quorum, sanity checks, and monitoring are still needed. Monotonicity is not on offer either: authenticated time is still wall-clock time and can move backward at a leap second or when a correction lands. Cloudflare's authoritative DNS hit this at the end of 2016 when a resolver round-trip measured across the leap second came out negative, propagated into upstream selection, and panicked Go's rand.Int63n(). Nothing was forged; the arithmetic simply went the wrong way. Code that measures elapsed time belongs on a monotonic clock.
Everything here is open source, including the protocol, the server, and the client, in Meta's Time library on GitHub. Meta is encouraging others — particularly anyone maintaining an NTP client on Android or iOS — to enable NTS support.
Where NTS stands in the ecosystem
Network Time Security reached the IETF Standards Track as a Proposed Standard in September 2020 with the publication of RFC 8915, Network Time Security for the Network Time Protocol. Cloudflare had already put time.cloudflare.com into service 15 months earlier, in June 2019, running the then-current draft and demonstrating that authenticated time holds up at scale.
On the server side, the public NTS list has grown to a few dozen entries, largely national metrology institutes and internet infrastructure operators — PTB, Netnod, and time.nl among them. Meta is now adding one more.
The client side is the bottleneck
Server coverage is no longer the hard part. Clients are. NTS is implemented where you would expect it in the server world: chrony and ntpsec both support it. The stock time clients on the platforms that account for most endpoints on the internet do not.
Android's platform time sync is plain SNTP over UDP/123, a fixed 48-byte packet with no extension-field handling, sitting alongside NITZ and GNSS. There is no NTS path in it. Apple devices sync through timed against time.apple.com, and we are not aware of any NTS support there either.
Those two stacks set the clock on billions of phones, watches and headsets, and every one of them is trusting an unauthenticated UDP packet.
A device vendor can ship a separate NTS-capable client today — chrony runs perfectly well on Android — but that is a workaround for something the platform should do itself. The protocol is finished, the servers are appearing, the client implementations exist. Mobile is the missing piece.
Meta's endpoint and code
nts.meta.com is live and free. The implementation lives in github.com/facebook/time.
If you run a public time service, turn NTS on. If you maintain an NTP client, implement RFC 8915. If you own the time stack in a mobile OS, this is the gap: Android and iOS are where most of the world's clocks get set, and both of them are still doing it over an unauthenticated packet.
Years of work on making time more precise leave one problem untouched: precision without authenticity only gets you a very accurate number from an unknown source. Now it can be both.
Acknowledgements
We'd like to thank Sofia Scalzo, Pablo Mazzini, Vadim Fedorenko, Patrick Cullen, and Yixin Wei for their support on this project.



