Fixing FreeBSD Time Sync Drift When ntpd Won't Converge
Why ntpd sometimes refuses to correct large clock offsets, how to read its own diagnostic output correctly, and when stepping the clock manually is the right call instead of waiting.
ntpd is deliberately conservative about how fast it corrects a system clock, and that conservatism is exactly what causes the most common “ntpd won’t fix my clock” complaints — the daemon isn’t broken, it’s refusing to do what it considers a dangerous correction, and the fix depends on understanding why.
ntpd’s step-vs-slew distinction, and why it matters here
ntpd treats small clock offsets and large ones completely differently. For small offsets (by default, under 128 milliseconds), it slews the clock — gradually speeding up or slowing down the system clock’s rate until it converges on the correct time, without ever making time jump backward or forward discontinuously, which would break anything relying on monotonically increasing timestamps. For large offsets, its default behavior is to refuse to slew (slewing a multi-minute offset would take an impractically long time) and instead exit or refuse to sync at all, on the theory that a huge offset is more likely to indicate a misconfigured system (wrong timezone, dead CMOS battery, a VM that’s been suspended for a long time) than something ntpd should silently paper over without an administrator noticing.
This is precisely why a system with a badly wrong clock (hours or days off, not milliseconds) often shows ntpd running, apparently healthy, and doing nothing to fix the problem — it’s working as designed, waiting for either a manual one-time step correction or an explicit configuration change authorizing it to step automatically.
Checking what ntpd actually believes right now
ntpq -p shows ntpd’s view of its configured time sources and how far off each one reports the local clock to be:
ntpq -p
The offset column, in milliseconds, is the number to look at first. If every configured source reports a similarly large offset, that’s strong evidence the local clock, not the network sources, is the actual problem. If sources disagree wildly with each other, that points at a network issue (an unreachable or misbehaving source) rather than the local clock. The leading character before each source’s hostname also matters: a * marks the currently-selected reference source, while sources with no leading character (or a rejected-reason character like x or -) aren’t currently being used to discipline the clock at all, regardless of whether they’re reachable.
Doing the one-time step correction
If the offset is large and ntpd is refusing to correct it, the direct fix is stopping ntpd, stepping the clock once with ntpdate (or ntpd -qg, which does a one-shot step-and-exit), and then restarting normal ntpd operation for ongoing discipline:
service ntpd stop
ntpd -qg
service ntpd start
ntpd -qg specifically tells ntpd to query configured servers once, step the clock however large the correction needs to be (the g flag lifts the normal step-size panic threshold for this one-shot run), and exit — after which the regular long-running ntpd service takes over for the small, continuous corrections it’s designed for. This two-phase approach (one-time step for a large jump, then continuous slewing for the small stuff afterward) is the intended pattern, not a workaround.
Configuring ntpd to step automatically going forward
For systems where a large offset might recur without an administrator present to run a manual step (freshly-provisioned VMs booting with a stale hardware clock, systems recovering from extended power-off periods), /etc/ntp.conf’s tos directive can raise the threshold at which ntpd will step automatically rather than refusing:
tos minsane 1
tinker step 3600 panic 0
tinker step raises the step threshold (in seconds) below which ntpd will correct via an immediate step rather than only slewing, and panic 0 disables the separate, larger “this offset is so extreme it’s probably a misconfiguration, refuse entirely and require manual intervention” threshold. Disabling the panic check entirely is a real tradeoff — it removes a safety net that exists specifically to catch grossly wrong clocks (a system whose clock somehow reads a decade off, for instance) before ntpd “corrects” it, so it’s appropriate for automated fleets where you’ve already got other monitoring for a badly wrong clock, and less appropriate for a single workstation where you’d rather be alerted to a wildly wrong clock than have it silently forced to match the network’s idea of time.
When the problem isn’t the offset threshold at all
If ntpq -p shows no usable sources at all (everything marked unreachable or rejected), the problem is upstream of ntpd’s step/slew logic entirely — check basic NTP reachability before assuming a configuration tuning problem:
ntpdate -q pool.ntp.org
A failure here points at network-level issues: outbound UDP port 123 blocked by a firewall (the local pf/ipfw ruleset, or a network-level firewall between the host and the internet), a DNS resolution failure for the configured server names, or, in virtualized/cloud environments, a host-provided NTP source that’s expected to be used instead of external pool servers. Confirming basic reachability first prevents chasing ntpd configuration tuning for a problem that’s actually a blocked port.
Verifying convergence afterward
After a step correction and with ntpd running normally, ntpq -p’s offset column should trend toward and stabilize near zero over the following minutes, not just immediately after the manual step. A clock that steps correctly but then drifts significantly again within a short window points at a different underlying problem — often a virtual machine whose host is itself under heavy load and delivering inconsistent virtual-clock ticks to the guest, which is a hypervisor-level timekeeping problem that ntpd tuning on the guest can partially compensate for but can’t fully solve, since the guest is disciplining a clock source that isn’t ticking at a consistent rate to begin with.