The agent reports configuration it ignores, and a robustness pass over scripting and concurrency
0.25.0 makes misconfiguration visible: a section or key nothing reads is now logged, shown in the web UI and reported by nscp settings --validate. Collector-backed checks gain a warmup-state option, kill tree finally works on Linux, and the rest of the release is the harvest of a stress harness, a concurrency audit and a full test of the Lua and Python APIs: a Lua crash under concurrent calls, socket requests stuck behind unrelated slow checks, duplicated events and client payloads, and a long list of smaller fixes.
✨ Highlights
- 🔍 Configuration nothing reads is reported. A misspelled section, a section without its leading
/, or a key above the first section used to be ignored without a word. The agent now warns about each one at startup and on every reload, the web UI shows a Configuration problems banner, andnscp settings --validateexits with1when it finds any (#647, #1657). - ⏱️
warmup-statefor collector-backed checks.check_cpu,check_loadand, on Linux and macOS,check_memory,check_pagefileandcheck_networkcan reportok(or any other status) instead of UNKNOWN while the collector takes its first sample, so a restart need not raise alerts. A collector that is disabled or failing now says so instead of reporting zeros (#1621). - 🐧
kill treeworks on Linux. The script runs in a session of its own and a timeout signals the whole process group, so backgrounded helpers die with it; unloading CheckExternalScripts now ends running scripts (#1629). - 🔒 Lua and Python scripting hardened. A Lua check running twice at once could crash the agent; listing scripts over REST ran every check; and a test of every documented API fixed a dozen bindings that did nothing or crashed (#1642, #1636, #1632).
- 📡 Socket servers no longer queue behind slow checks. About one NRPE, NSCA, check_mk or NSClient request in a few hundred waited for an unrelated long-running check, which showed up as an occasional
Socket timeout(#1642). - 🔒 OpenSSL 3.5.9 in the Windows builds, from the 29 September 2026 upstream security release (#1666).
🔍 Detailed changes
⚙️ Settings — configuration nothing reads (#647)
A key the agent never reads used to be silently ignored, so a script defined under [settings/external scripts] (no leading /) was simply Unknown command. Every section and key is now checked against what the loaded modules registered:
| Where | What you see |
|---|---|
| Log | a warning naming each unread section or key, at startup and on every reload |
| Web UI | a Configuration problems banner, and a Problems button on the Settings page |
| REST | GET /api/v2/settings/problems
|
| CLI | nscp settings --validate prints the report and exits with 1 on a warning
|
Sections and keys that belong to a module which is not loaded are listed as info and raise nothing; add --load-all to --validate to have every installed module register its settings first. The check found one of its own: the core registered its section as settings rather than /settings, so nscp settings --update wrote an empty [settings] into every file it touched. It no longer does, and an existing empty one is harmless.
⏱️ CheckSystem / CheckSystemUnix — warmup-state
Right after a start the background collector has no sample yet and the checks answer UNKNOWN "… not available yet (collector still initializing)". The new option picks that status:
check_cpu warmup-state=ok
It only covers a collector that has not tried yet. Telling the cases apart also changes results without the option: on Linux and macOS a source the collector cannot read (an unreadable /proc/stat or /proc/meminfo in a locked-down container) used to be stored as an all-zero sample, so check_cpu reported 0% load as if measured; it now answers UNKNOWN with the reason. The checks concept page describes every case.
🐧 CheckExternalScripts — kill tree on Linux
The setting was accepted on Linux but did nothing: only the script was signalled on a timeout, and a helper it had backgrounded kept the output pipe open, so the check only returned when the timeout expired. With kill tree = true the script now runs in its own session and SIGTERM / SIGKILL go to the whole group. Unloading the module kills running scripts, which report was killed: the module is unloading; a script ended by a signal names it (was terminated by signal 9 (SIGKILL)), and every script reads stdin from /dev/null.
🔒 Scripting — LUAScript and PythonScript
- Concurrent Lua calls. Two calls into the same script, where the first was parked in
nscp.sleepor aCorecall, shared one Lua stack and could terminate the agent; two overlapping NRPE requests were enough. Every call now runs on a Lua thread of its own. - Listing scripts ran every check.
GET /api/v2/scripts/luaand/api/v2/scripts/pycollected each query's parameters by running it, so a caller with only a listing grant could run every check on the agent. They now ask for names only. - Show and delete.
GET/DELETEon/api/v2/scripts/{lua,py}/<name>(andnscp lua|py show|delete) answered an empty200and did nothing. They now read and delete script files as documented; the security notice lists what that means for the grants. - Lua API. One script that failed to parse no longer takes the whole module down; an
on_startcalling the Core API no longer leaves the Lua lock held for good;Registry:simple_cmdlinehandlers run; a reload re-runson_start;require()searches${scripts}/lua/libas documented, which fixes shared helpers on Linux packages. - Python API.
plugin_aliasispythonrather than empty when no alias is set; aRegistry.eventhandler no longer crashes the agent andRegistry.event_pbhandlers run;Core.simple_execreports a call nothing could run as UNKNOWN instead of(0, []). nscp lua listandnscp py listwere always empty on DEB/RPM installs, where${scripts}lives under/usr/lib; they now list the scripts.
📡 Socket servers and the core — concurrency
- No queueing behind slow checks. The NRPE, NSCA, check_mk and NSClient servers used legacy asio strands, which share a pool of 193 implementations between all connections, so a new request could wait for another connection's check. Every connection and the accept loop now has a strand of its own.
- Events delivered once. A message carrying N records (a real-time CPU filter sends one per core) reached every subscriber N times:
/api/v2/events, ElasticClient and script handlers all saw duplicates. - Client targets send once. Since 0.20.0 a client query with
target=<name>sent its payload twice:check_foo!a!aover NRPE, everysubmit_*message as two lines, everybatch=record twice. - Reloads start modules after loading them. A Lua
on_startor a Schedulerrun on startupschedule now runs once every module the reload loads is loaded, as at boot, and a module a reload loads for the first time is started. - Module unload waits for calls inside it. Unloading now waits for a log line or a metrics/facts round still running inside the module, and refuses with a clear message rather than tearing it down underneath.
- Settings and permission reload races. Walks of the included settings stores raced a reload (a use-after-free); a permission policy reload that fails part-way now fails closed and denies exec.
📝 Logging — one logger
The core now has one logger instead of three backends. This is mostly an internal cleanup, and the log looks the same on a normal install. Two things differ: the service writes each line to the console and the log file on the thread that logs it rather than through a queue, so the last lines before a crash are no longer left behind in that queue; and the queue that feeds log-handler modules (the web UI's live log, check_nscp, ElasticClient, scripts) is capped at 10 000 lines, past which the oldest lines are dropped for the handlers only and nsclient.fatal records it. --log-backend file and threaded-file both still work, so installed service units need no change (#1637).
🛡️ Undefined-behaviour sweep and audit follow-ups
The second undefined-behaviour sweep and the 2026-09 source review closed another round of findings (#1609, #1622,
#1623, #1630, #1639, #1640):
convert()/neg()results that overflow 64 bits are an evaluation error instead of a wrapped value, andconvert(2, 'min')/convert(2, 'kb')now scale whole numbers as they did fractions.- Out-of-range numbers from
check_http json-path=, Docker, Kubernetes and floating-point keywords saturate at the integer limit. - Whitespace-only perfdata parses as empty, and
filter_perfsorts non-numeric entries last. - NRPE escapes control characters in its log lines, check_nt logs a missing password once at load, and the About page links only to
http(s)homepages. - The docs now name the web grants and the
USER_WRITABLE_CONFIGinstaller property that amount to administrator access, and the unreachable root/metricsroute is removed.
🐛 Bug fixes
check_pdhand a missing instance. A counter instance that disappears for a moment is sampled once more a second later, errors name the counter (Failed to poll counter: No data for <path> ...), andignore-errors=truenow skips a counter with no value (#648, #1654, #1625).- Rejected connections say why. One log line names the address, the
allowed hostslist and the section that sets it (#1651). - Fleet host override wins. The fleet server's host-specific override is now merged after the bundles, so it wins on a key both set (#1635).
- Aliases with
\nare refused instead of silently gaining a newline, so"path=C:\nscp"no longer becomesC:+ newline +scp(#1649). - check_nt explains "No performance data" with the check's own message, and an
&in a file or service name no longer splits the reply (#1649). check_os_versionno longer swaps Windows 7 and Server 2008 R2, and names Windows 11 25H2 (#1649).- Localized Windows. OS error text in the ANSI code page no longer breaks the protobuf result, which lost the whole check result on a non-English host (#1619).
- CheckMKClient
scriptsentries resolve under${scripts}and a missing one is logged.
📦 Packaging
- The Windows builds bundle OpenSSL 3.5.9 (from 3.5.8), picking up CVE-2026-35189 and clearing the version-based scanner finding (#1665, #1666). Linux packages use the distribution's OpenSSL and are unaffected.
⚠️ Upgrade notes
The full list is on the upgrading page, and the security fixes on the
security notices page.
nscp settings --validatecan now exit with1. A script or pipeline that runs it fails on any unread section or key; fix the reported entries.- Fleet host overrides now win over bundles. If a host override sets a key a bundle also sets, the host switches to the override's value; check your overrides if you relied on the bundle winning.
- Aliases containing
\nare refused. Check the log forRefusing aliasafter upgrading; write\\nfor a literal, and Windows paths with/or doubled backslashes. - Log-matching rules. The rejected connection lines (now one line), check_nt's No performance data error and
check_pdh'sFailed to add countertext changed; match the new prefixes. - LUAScript reloads re-run
on_start, and Luarequire()/ CheckMKClient scripts resolve from${scripts}; check scripts that must run once, or helpers kept under the install base with a custom${scripts}. - PythonScript passes
pythonasplugin_aliasandCore.simple_execreports failures as UNKNOWN; check scripts that test for"",0orFalse. - Linux
kill tree = truenow runs the script in its own session; a script that callssetsid()itself getsEPERM. - Permission policy: a reload that fails part-way now denies exec until a later reload succeeds. Nothing changes unless
[/settings/permissions] enabled = true.
Full Changelog: 0.24.1...0.25.0