EN FR Home

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 ~ or marginal).
  • 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 prefixMeaning
*System peer — the selected source
+Candidate — in the combine set
-Outlyer — rejected by clustering algorithm
xFalse 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:

  1. Do not simply remove the peer — replace it. You want 4+ active peers at all times.
  2. Pick a replacement on a different operator and different ASN than your other peers. See our dual-operator options.
  3. Prefer NTS-authenticated sources when available — they prevent an attacker-in-the-middle from turning a truechimer into a false ticker.
  4. 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.org DNS 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: