NTP False Ticker Detection & Fix
Identify the rogue source, replace it, keep the majority truthful
1. What a false ticker is (and is not)
In NTP parlance, a truechimer is a source whose reported time agrees with the majority of other sources within the protocol's correctness tolerance. A false ticker is a source that disagrees — either because it is wrong, or because the path to it is so asymmetric that the measurement lies about it.
What a false ticker is NOT:
- A source with high jitter — that is an unreliable but potentially correct source (tagged
~ormarginal). - A source with high stratum — stratum only measures distance to the atomic reference, not correctness.
- A source with reach 0 — that is unreachable, not false; no measurement has occurred.
2. How the Marzullo-Mills selection flags it
For each peer, NTP constructs a correctness interval: [offset − root_distance, offset + root_distance] where root_distance = root_delay/2 + root_dispersion. This interval expresses "the true time lies somewhere in here, and I am at least this wide about it".
The Marzullo-Mills algorithm (RFC 5905 §11.2.1) finds the largest subset of peers whose intervals share a common point. Peers inside that intersection are truechimers; peers outside are false tickers.
3. Decode the ntpq "x" prefix and chronyc states
ntpq prefixes each peer with a 1-character state flag. The relevant ones for false-ticker diagnosis:
| ntpq prefix | Meaning |
|---|---|
* | System peer — the selected source |
+ | Candidate — in the combine set |
- | Outlyer — rejected by clustering algorithm |
x | False ticker — rejected by intersection |
. | Too-many-survivors reject |
# | Good but not used (too many candidates) |
(space) | Reject (unreachable, bad sanity, high stratum) |
chronyc uses two columns: M (mode: ^ server, = peer) and S (state):
MS Name/IP address
^* ntp.rdem-systems.com synced, current sync source
^+ time.cloudflare.com combined
^- time.google.com rejected by cluster
^x rogue-server.example.com FALSE TICKER
^? down-server.example.com connectivity lost
^~ jittery.example.com too variable
4. The 4 real-world causes
Cause A — upstream server is actually off
The most common cause. A Stratum 2 whose own upstream went into stratum 16, a server running systemd-timesyncd without backup, a pool member with a broken GPS — all can produce seconds of real offset. Check the source's own chronyc tracking / ntpq -c rv from the operator side, or query it from an independent vantage point with ntpdate -q.
Cause B — asymmetric path
If the path to one peer is via a tunnel (IPsec, VPN, GRE) while the other peers are direct, the one-way latencies are wildly different. NTP assumes symmetry; asymmetry shows up as offset, and can make an otherwise-correct source look like a liar.
Cause C — inflated root distance
A peer that reports a large root_delay or root_dispersion has a wide correctness interval. If its offset is close to correct but its interval is wider than the overlap of the others, Marzullo-Mills may still exclude it as a safety measure (it cannot narrow the overlap, so it is rejected as non-contributing).
Cause D — insufficient peer count
With only 3 peers, any single real disagreement creates an even split. Two agreeing peers are not enough for Marzullo-Mills to feel confident discarding the third — so all three can end up flagged or selection fails entirely. Fix: always configure 4+ peers in production.
5. Confirm which source is actually wrong
Before editing ntp.conf, confirm who is telling the truth.
# Get offsets for every configured source
$ chronyc sourcestats -v
# Query each source independently
$ for s in ntp.rdem-systems.com time.cloudflare.com ntp1.ptb.de suspect.example.com; do
echo -n "$s: "
ntpdate -q "$s" 2>&1 | grep offset
done
# Cross-check against this live validator
$ curl -s https://ntp.rdem-systems.com/time-api.php
Compare the offsets. The one that diverges is your false ticker.
6. Replace without creating a new deadlock
Once identified:
- Do not simply remove the peer — replace it. You want 4+ active peers at all times.
- Pick a replacement on a different operator and different ASN than your other peers. See our dual-operator options.
- Prefer NTS-authenticated sources when available — they prevent an attacker-in-the-middle from turning a truechimer into a false ticker.
- After reloading chrony/ntpd, wait 4–8 poll intervals (~5–15 min) for the new peer to reach 377 and for selection to stabilise.
# Sample chrony.conf with 4 diversified sources (3 NTS, 1 pool)
server ntp.rdem-systems.com iburst nts # FR, AS206014
server time.cloudflare.com iburst nts # anycast
server nts.netnod.se iburst nts # SE, Netnod
pool 2.pool.ntp.org iburst
makestep 1.0 3
rtcsync
7. Security: when a false ticker is an attack
Without NTS, UDP 123 is unauthenticated. An on-path attacker can:
- Respond faster than the legitimate server and win the race,
- Inject forged responses under the server's IP,
- Poison the
pool.ntp.orgDNS result and substitute a rogue server.
Signs that a false ticker is an attack rather than a bug:
- The "false" offset is in the direction of expired TLS certificates (future) or expired Kerberos tickets (past).
- The rogue source is one of the pool entries, and changes every minute.
- Response source IP appears legitimate but TTL or routing differs.
After the fix — related sites:
- Measure jitter, offset, latency → ntp-tester.eu
- Produce NIS 2 / ISO 27001 audit evidence → online-ntp-validator.com
- Enterprise NTS deployment → ntp.rdem-systems.com/nts