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, or a Windows host whose w32time service runs the default low-accuracy config — 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:

Frequently asked questions

What is an NTP false ticker?

A false ticker is an NTP source that the client has decided cannot be trusted because its reported time disagrees with the majority of the other configured sources. RFC 5905 Section 11.2 (the selection algorithm) flags it by the 'x' prefix in ntpq -p, or exclusion from the selected set in chronyc. A false ticker contributes nothing to the combined clock — it is effectively ignored until it agrees again.

How does NTP decide which source is the false ticker?

The Marzullo-Mills algorithm takes every source's [offset - root_distance, offset + root_distance] correctness interval, and picks the largest subset that overlaps on a common point in time. Sources inside that overlap are 'truechimers'; sources outside are false tickers. The algorithm works with as few as 3 sources but becomes robust only at 4+ — with fewer, a single bad source can steal the majority.

Why would a healthy-looking NTP source be flagged false?

Four common reasons: (1) the upstream server is actually off by seconds (misconfigured or compromised), (2) path asymmetry pushes the measured offset out of the correctness interval of the other peers, (3) the peer reports an inflated root distance that makes its interval too wide to overlap, (4) you only have 3 sources and one disagrees — selection deadlocks. Our stratum-16 page covers the deadlock scenario.

How do I identify the false ticker on chrony?

Run 'chronyc sources'. The column 'M' (mode) and 'S' (state) tell you: '*' = current synchronisation source, '+' = selected for combine, '-' = rejected by combine algorithm, '?' = connectivity lost, 'x' = falseticker, '~' = too variable. Any source with 'x' is a false ticker. Cross-check with 'chronyc sourcestats' to see its offset versus the selected source.

Should I just remove the false ticker from ntp.conf?

Removing without understanding is unsafe — you may delete a truthful source and keep the liars. First run 'chronyc sourcestats' on all sources and compare offset to a known-good reference (this validator, time.cloudflare.com, or a GPS clock). Replace the confirmed-wrong source with one from a distinct operator and AS. Never run NTP with fewer than 4 sources in production.