louislam/uptime-kuma · warning · Error
Time offset ${offset.toFixed(3)}ms exceeds threshold ${offse
Error message
Time offset ${offset.toFixed(3)}ms exceeds threshold ${offsetThreshold}ms What it means
Thrown when abs(offset) >= monitor.ntp_time_offset_threshold (default 1000 ms). 'offset' is the client-server clock offset computed per RFC 5905: ((T2-T1)+(T3-T4))/2. A large absolute offset means the Uptime Kuma host's clock and the target NTP server disagree beyond the allowed tolerance.
Source
Thrown at server/monitor-types/ntp.js:46
heartbeat.ping = dayjs().valueOf() - startTime;
const { stratum, offset, rootDispersion, refid, roundTripDelay } = ntpResult;
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 dataView on GitHub (pinned to 6b5ea01557)
Solutions
- Enable time sync on the Uptime Kuma host (install chrony/ntp, or enable the hypervisor's time integration).
- Point the monitor at a geographically closer NTP server to cut asymmetric delay.
- If the skew is expected/benign, raise monitor.ntp_time_offset_threshold.
- Run 'chronyc tracking' / 'ntpq -p' on the monitoring host to confirm its own offset is small.
Example fix
// before: monitor.ntp_time_offset_threshold = 1000 (cross-region link) // after: monitor.ntp_time_offset_threshold = 5000 // and on the kuma host: sudo apt-get install -y chrony && sudo systemctl enable --now chrony
Defensive patterns
Strategy: validation
Validate before calling
function validateOffsetThreshold(monitor) {
const t = Number(monitor.ntp_time_offset_threshold ?? 1000);
if (!Number.isFinite(t) || t <= 0) throw new Error('ntp_time_offset_threshold must be > 0');
} Prevention
- Run chrony/ntpd on the Uptime Kuma host so its clock doesn't drift.
- Set ntp_time_offset_threshold based on measured baseline offset to the target.
- Prefer a geographically close NTP server to reduce asymmetric delay.
When it happens
Trigger: queryNTP succeeds, |offset| >= offsetThreshold. Caused by the monitoring host's clock drifting (common on VMs/containers without working time sync), large asymmetric network delay, or the target itself serving skewed time.
Common situations: Container/VM host without ntpd/chronyd, so it drifts; high-latency or jittery WAN to the NTP server; monitoring a stratum-1 across an intercontinental link; host clock stepped by kvmclock/PTP incorrectly.
Related errors
- Stratum ${stratum} meets or exceeds threshold ${stratumThres
- Root dispersion ${rootDispersion.toFixed(3)}ms exceeds thres
- Hostname is required
- NTP server is unsynchronized (stratum 16)
- Invalid NTP response: expected 48+ bytes, got ${msg.length}
AI-assisted analysis of louislam/uptime-kuma@6b5ea01557 (2026-08-12).
Data as JSON: /api/errors/f9c3204d314609fd.
Report an issue: GitHub.