louislam/uptime-kuma · warning · Error

Root dispersion ${rootDispersion.toFixed(3)}ms exceeds thres

Error message

Root dispersion ${rootDispersion.toFixed(3)}ms exceeds threshold ${dispersionThreshold}ms

What it means

Thrown when rootDispersion >= monitor.ntp_root_dispersion_threshold (default 500 ms). Root dispersion is the server's reported accumulated measurement error (read as a 32-bit fixed-point at packet offset 8 and converted to ms). High dispersion indicates the server itself is uncertain about its time, independent of the offset to this client.

Source

Thrown at server/monitor-types/ntp.js:51

        heartbeat.msg = `Stratum: ${stratum}, RefID: ${refid}, Offset: ${offset.toFixed(3)}ms, Delay: ${roundTripDelay.toFixed(3)}ms, Dispersion: ${rootDispersion.toFixed(3)}ms`;

        if (stratum === 16) {
            throw new Error("NTP server is unsynchronized (stratum 16)");
        }

        const stratumThreshold = monitor.ntp_stratum_threshold || 5;
        if (stratum >= stratumThreshold) {
            throw new Error(`Stratum ${stratum} meets or exceeds threshold ${stratumThreshold}`);
        }

        const offsetThreshold = monitor.ntp_time_offset_threshold || 1000;
        if (Math.abs(offset) >= offsetThreshold) {
            throw new Error(`Time offset ${offset.toFixed(3)}ms exceeds threshold ${offsetThreshold}ms`);
        }

        const dispersionThreshold = monitor.ntp_root_dispersion_threshold || 500;
        if (rootDispersion >= dispersionThreshold) {
            throw new Error(
                `Root dispersion ${rootDispersion.toFixed(3)}ms exceeds threshold ${dispersionThreshold}ms`
            );
        }

        heartbeat.status = UP;
    }

    /**
     * Query an NTP server via UDP
     * @param {string} hostname NTP server hostname or IP
     * @param {number} port NTP server port (usually 123)
     * @param {number} timeout Timeout in milliseconds
     * @returns {Promise<object>} Parsed NTP response data
     */
    queryNTP(hostname, port, timeout) {
        return new Promise((resolve, reject) => {
            let client = null;
            let settled = false;

View on GitHub (pinned to 6b5ea01557)

Solutions

  1. Raise monitor.ntp_root_dispersion_threshold to a value the target normally stays under.
  2. Switch to a more stable, lower-stratum NTP source.
  3. Confirm the target daemon has had time to converge after restart.

Example fix

// before: monitor.ntp_root_dispersion_threshold = 500
// after:  monitor.ntp_root_dispersion_threshold = 2000
Defensive patterns

Strategy: validation

Validate before calling

function validateDispersionThreshold(monitor) {
  const t = Number(monitor.ntp_root_dispersion_threshold ?? 500);
  if (!Number.isFinite(t) || t <= 0) throw new Error('ntp_root_dispersion_threshold must be > 0');
}

Prevention

When it happens

Trigger: Server returns a valid packet whose root-dispersion field, converted to ms, meets or exceeds dispersionThreshold. Happens with unstable server chains, recently restarted daemons whose filters haven't converged, or misbehaving appliance servers.

Common situations: Default 500 ms threshold too strict for a legitimate distant server; target's dispersion genuinely high due to poor upstream stability; software/appliance NTP reporting inflated dispersion.

Related errors


AI-assisted analysis of louislam/uptime-kuma@6b5ea01557 (2026-08-12). Data as JSON: /api/errors/544da1b0b20db5ce. Report an issue: GitHub.