Skip to content

News

0.18.1 Security hardening and bugfixes of monitoring clients

0.18.1 is a security and correctness release for the passive/outbound side of the agent. A review pass over the client modules โ€” Icinga, NRDP, NSCA, NSCA-NG, check_mk, Elastic, Graphite, syslog, and a second round on SMTP โ€” turned up the same three shapes over and over: configuration that parsed fine and was then thrown away, network operations that could never time out, and attacker-influenced text reaching another system’s log unscrubbed. The external-script launcher and the filter framework got the same treatment.

The most consequential single item is NSCA-NG cert mode, which applied its TLS configuration too late and therefore accepted any server certificate. Alongside the hardening, the Elastic module can finally talk to a current Elasticsearch, the web UI moves to MUI 9 / react-router 8 with its test suites wired into CI, and a coverage sweep adds unit tests to every source file that was under 50%.

โœจ Highlights

  • ๐Ÿ” NSCA-NG cert mode verifies the server again โ€” and presents your client certificate. The OpenSSL context was configured after the TLS stream was created from it, and SSL_new() copies the verify mode, certificate, cipher list and version bounds at creation time. So verify mode = peer-cert ran with verification off, any certificate was accepted, and the configured client certificate was never sent. The default PSK mode was never affected. (#1461)
  • ๐Ÿงพ Configuration that silently did nothing now applies. check_mk client targets discarded every TLS key (use ssl, certificate, verify mode, โ€ฆ) and connected in plaintext regardless; syslog targets never read severity, facility, tag_syntax or message_syntax; NSCAServer’s performance data = false was ignored; and ext-scr install --arguments=โ€ฆ wrote its lockdown to a key the module does not read. All four are fixed.
  • โฑ๏ธ A stalled endpoint can no longer wedge a submitting thread forever. Icinga, NRDP, Graphite and Elastic submissions all ran with no deadline; each is now bounded by the target’s timeout (default 30 s) as a single budget over resolve, connect, handshake and exchange. External scripts enforce their timeout by wall clock on both platforms. (#1464, #1465, #1466, #1467, #1468, #1453)
  • ๐Ÿ”Ž The Elastic module works against Elasticsearch 8 โ€” and verifies TLS. Verification was hardcoded off; it now defaults to peer with tls version, verify mode and ca settings, plus new user/password and api key authentication. The legacy _type parameter is no longer sent by default, and every document in a bulk request gets its own _id โ€” previously they shared one and overwrote each other. (#1453)
  • ๐Ÿ”’ tls version = 1.2+ means “1.2 or later” again. The + was stripped and the value mapped onto a version-pinned method, which pins the maximum too โ€” so the common default negotiated TLS 1.2 only and silently excluded TLS 1.3. Fixed in the shared stack: all HTTP-based clients, the NRPE/NSCA socket clients and servers, and check_tcp. (#1464)
  • ๐Ÿ“จ Syslog datagrams carry the RFC 3164 HOSTNAME field, so a conforming receiver stops promoting the tag to origin host โ€” which meant check output could choose which host a record was filed under. An unknown severity or facility now degrades to <13> (user.notice) instead of <0>, kernel.emergency. (#1470)
  • ๐Ÿงฑ Filter expressions are bounded at 1024 characters and 64 nesting levels. The parser and the AST evaluator both recurse with the shape of the input, so a long or deeply nested expression could exhaust the stack and crash the agent. Real filters sit an order of magnitude below the limits. (#1469)
  • ๐Ÿ–ฅ๏ธ The web UI moves to MUI 9, react-router 8 and TypeScript 6, with its 65 vitest unit tests and 18 Playwright integration tests now running in CI on every build. (#1459)

๐Ÿ” Detailed changes

๐Ÿ” NSCA-NG โ€” cert mode applies TLS configuration before the stream exists

use psk = false targets built the ssl::context, created the connection from it, and then set the verify mode, client certificate, cipher list and TLS version bounds. OpenSSL copies all of that out of the context when the stream is created, so none of it took effect: verify mode = peer-cert accepted a man-in-the-middle’s certificate, and a server asking for a client certificate never got one. Configuration is applied first now.

Two visible consequences: a cert-mode target whose server certificate does not chain to the configured ca (or does not match the host name) will now fail to connect โ€” that is the verification working โ€” and servers requiring a client certificate will start receiving it. The default PSK mode authenticates both ends through the pre-shared key and is unaffected.

๐Ÿงพ Settings that were read but never applied

Module Setting(s) What happened
CheckMKClient use ssl, certificate, certificate key, ca, allowed ciphers, verify mode, dh The target object never called register_all()/notify(), so the keys were parsed and thrown away โ€” the client connected in plaintext whatever the configuration said, and the keys were missing from the reference docs.
SyslogClient severity, facility, tag_syntax, message_syntax, ok-severity/warning-severity/critical-severity/unknown-severity Stored under keys the sender never read, so built-in defaults always won: a settings-defined target sent an empty tag and dropped the message text.
NSCAServer performance data = false Ignored; perfdata is stripped from forwarded submissions again, as documented.
CheckExternalScripts ext-scr install --arguments=โ€ฆ Wrote the lockdown to a path nothing reads, so it reported success while leaving arguments enabled. Re-run it after upgrading.
GraphiteClient timeout Looked up in the free-form option map, where the well-known timeout key is not stored โ€” the default 30 always won.

Values you configured โ€” possibly years ago, without effect โ€” now apply. Review those target sections for stale keys before upgrading.

โฑ๏ธ Operations that could never time out

Each of these ran with no deadline, so an endpoint that accepted the connection and then went silent held the submitting thread indefinitely, quietly stopping passive results until a service restart.

  • Icinga, NRDP, Graphite (both the submission and the recurring metrics flush) and Elastic are now bounded by the configured timeout (default 30 s; 10 s for one-shot nscp client submissions) as one budget covering name resolution, connect, TLS handshake and the exchange. NRDP also retries transport failures up to retry, each attempt on a fresh connection.
  • Graphite’s retry is gone. It never had any effect โ€” the module always made exactly one attempt โ€” and a retry loop would multiply the worst-case time a stalled endpoint can hold a thread. It is still registered centrally for all client modules, so it remains in the reference, but GraphiteClient does not act on it. Mirrors the SMTP retry change in 0.18.0.
  • External scripts. On Unix the single-string shell fallback ran through popen(), which hides the child PID, so timeout= was unenforced and a hung script wedged a worker thread per invocation. On Windows the read loop counted iterations rather than elapsed time, so a continuously chatty script escaped the timeout entirely and leaked an unkillable process each run. Both launchers now bound the wait by wall-clock deadline, with captured output capped at 8 MiB.
  • SMTP already bounded its submission, but a budget that expired mid-connect left the cancelled operation’s completion handler queued โ€” to be run by the retry against the next resolved address, with references into a stack frame that no longer existed. Handler state is heap-owned now, and a spent budget ends the endpoint walk instead of retrying into it. (#1471)

๐Ÿงน Injection, scrubbing and resource limits

  • Graphite status paths go through the same scrub as the perf path. The ${check_alias} substituted into them can come from a remote submitter, so an alias carrying a newline injected an extra, attacker-chosen metric line into Graphite (and a ; injected carbon tags) โ€” a way to hide a real problem or fabricate one. (#1465, #1467)
  • Inbound NSCA wire fields are validated before they reach the logs and the inbox channel: control characters are stripped from host and service names, and a return code outside 0โ€“3 is clamped to UNKNOWN instead of flowing on as an arbitrary 16-bit integer. (#1460)
  • SMTP reply text is rendered inert before it is logged. Anything outside printable US-ASCII is replaced โ€” the C0 controls and the C1 range (0x80โ€“0x9F), which carries single-byte terminal escapes such as CSI โ€” so a multi-line reply can no longer forge extra log lines. The reply to STARTTLS must be exactly 220 per RFC 3207 rather than any 2xx, and AUTH credentials containing a NUL are refused before connecting. (#1471)
  • Buffers are bounded. An NRDP response body is capped at 5 MB (previously unbounded โ€” a hostile server, or a man in the middle on a plain http:// target, could stream the agent out of memory), and an SMTP reply at 64 KB per line and 100 lines. Nothing previously capped how much a peer could make the client buffer inside its timeout window, so bytes without a line ending, or endless 250- continuations, turned a 30-second budget into gigabytes.
  • Credentials are masked in the trace log. The trace-level target dump printed raw password / token values; the fix is in the shared client machinery, so every outbound client module is covered. (#1466)
  • Unverified links are called out. An Icinga https submission whose verify mode resolves to no peer verification logs a message naming the endpoint. An empty NSCA password with encryption enabled logs an error on both ends โ€” the key is the password zero-padded with no derivation step, so an empty one is a well-known all-zero key.

๐Ÿ”Ž ElasticClient โ€” verified, authenticated, and Elasticsearch 8 compatible

Setting Default Purpose
tls version 1.2+ TLS floor for https:// addresses
verify mode peer Certificate verification (was hardcoded to none)
ca ${ca-path} CA bundle
user / password (empty) HTTP basic authentication
api key (empty) API-key authentication
timeout 30 Budget for the whole submission
event type, metrics type, nsclient log type (now empty) Legacy _type; set explicitly on ES 6.x or older

Beyond the TLS and auth work: every document in a bulk request now gets its own _id, so multi-line events show up completely instead of overwriting each other down to a single entry; responses are parsed defensively and non-2xx statuses are reported instead of ignored; a timestamp from one event line no longer leaks into later lines; and events are refused after unloadModule. (#1453)

๐Ÿ“จ SyslogClient โ€” a well-formed datagram, and options that reach the wire

Datagrams now read <PRI>TIMESTAMP HOSTNAME TAG MESSAGE. The hostname setting under [/settings/syslog/client] โ€” until now read but never used โ€” fills the HOSTNAME field (default auto, the machine name). Receivers that promoted the tag (default NSCA) to origin host will now file records under the real host name, so adjust any log-parsing rule keyed on the old, hostname-less format. An IPv6 hostname is kept intact.

tag_syntax, message_syntax and the per-state severity options take effect for the first time; an unknown severity/facility falls back to <13> instead of <0>; and all C0 control bytes and DEL in the outgoing line are replaced with spaces (previously only CR, LF and NUL), so check output cannot smuggle ANSI escape sequences into the receiver’s log. (#1470)

๐Ÿงฑ Filter framework โ€” bounded expression length and depth

A filter / warning / critical expression โ€” and a %(...) expression placeholder inside a syntax template โ€” longer than 1024 characters or nested more than 64 parentheses deep is rejected at parse time with a clear “exceeds the maximum length/depth” error. Both the recursive-descent parser and the AST evaluator recurse with the shape of the input, so an unbounded or deeply nested expression could exhaust the thread stack and crash the whole agent โ€” reachable by anyone able to influence a filter string over the authenticated REST API, or over NRPE with allow arguments = true. String literals are exempt from the depth count, and real filters are a small fraction of both limits. (#1469)

๐Ÿš CheckExternalScripts โ€” sandbox, arguments and the shell fallback

Beyond the timeout work above: the show / delete sandbox resolves symlinks before its containment test (previously a symlink inside the script root pointing outside it let an authenticated admin read or remove files anywhere the service account could reach); % and ^ are refused on the Windows shell fallback (cmd.exe %VAR% expansion and its escape character, opt-out via allow nasty characters); add arguments is honoured and list --include-lib works; and a null-provider dereference and the help-pb argument numbering are fixed. The docs now warn that write access to any script path directory is equivalent to code execution as the service account, and clarify that allow arguments does not gate aliases. (#1468)

๐Ÿ–ฅ๏ธ Web UI โ€” dependency modernization

Every dependency in web/package.json moves to its latest release: MUI (material, icons, x-charts) 7/8 โ†’ 9, react-router 7 โ†’ 8, eslint 9 โ†’ 10, TypeScript 5.9 โ†’ 6.0, plus minor bumps for react, redux, zod, vite and vitest. The UI is adapted to the MUI 9 breaking changes โ€” removed system props moved into sx, renamed outlined icons, the containedPrimary shadow expressed as a theme variant, and Autocomplete’s renderInput params now exposing slotProps.input.

Both web suites are now part of the CI build (build-web.yml, Node raised to 22 for react-router 8): 65 vitest unit tests and 18 Playwright integration tests driving the built bundle in a real Chromium. The e2e preview server binds to 127.0.0.1. (#1459)

๐Ÿงช Tests and coverage

A sweep of the gcovr reports added unit tests to every source file under 50% combined line coverage that can be exercised deterministically โ€” roughly 4,000 lines of new test code across the check_mk wire protocol, pid_file, the compat helpers, execute_process_unix, the NRDP/NSCA-ng/NSCP client handlers, Icinga target objects, the CheckDisk file filter, perf_filter, the where-engine evaluation context, the external-scripts provider, CheckSystemUnix network / service / cpu-frequency, the simple file logger, the zip plugin, onboarding’s chown_subtree and the settings proxy. New integration suites cover CheckExternalScripts commands, Elastic submission and NSCA-NG cert mode.

check_cpu_frequency was refactored to take a sysfs base path so a fixture tree can drive it; production behaviour is unchanged.

๐Ÿ› Bug fixes

  • NSClientServer: check_nt instance listing could crash the serving I/O thread. list_instance() advanced a tokenizer iterator under a guard that was always true, making the invalid-line branch dead and dereferencing tok.end() on any line with fewer than three comma-separated fields โ€” which a failed PDH enumeration produces (ERROR: โ€ฆ). It now advances to the third field explicitly and logs genuinely malformed lines. (#1463)
  • NSCA-NG scenario docs corrected. The example nsca-ng.cfg used command instead of command_file (so it did not parse) and carried an authorize block with only a password, which nsca-ng treats as authorizing nothing โ€” the PSK handshake succeeds and every submission is rejected with FAIL. The example now has anchored hosts/services patterns, a Common Gotchas entry for that symptom, a danger admonition against wildcard commands patterns, and guidance on PSK entropy and not passing --password on the command line. (#1462)
  • Elastic: dead copy-paste from the Graphite client removed (elastic_handler.hpp, the unused channel/command machinery and the client-parser dependency).
  • execute_process_w32 uses GetTickCount for XP-toolset compatibility.

โš ๏ธ Upgrade notes

  • ๐Ÿ”’ NSCA-NG cert mode really verifies now. Upgrade if any target sets use psk = false. A target whose server certificate does not chain to the configured ca, or does not match the host name, will start failing to connect โ€” fix the certificate, or accept the exposure explicitly with insecure = true. Servers requiring a client certificate will now receive one. PSK mode is unaffected.
  • ๐Ÿ”’ check_mk targets with use ssl = true now really negotiate TLS. A plaintext-only server end will start failing โ€” loudly, which is the point.
  • ๐Ÿ”’ An unrecognized NSCA encryption value is a hard error. A typo (aes-256) or an algorithm not compiled into the build used to fall back to no encryption on the end carrying it. The NSCAServer module now refuses to load and an NSCAClient submission fails, each naming the available algorithms. Breaking only for setups relying on that fallback โ€” including builds compiled without crypto++, where every cipher name degraded to plaintext. Fix the name, or set encryption = none if plaintext was intended. Default installs (aes256) are unaffected.
  • ๐Ÿ”’ An empty NSCA password with encryption enabled now logs an error on both ends. The password is the key, so an empty one is a well-known key โ€” set the same real password on both ends.
  • NSCAServer’s performance data = false is honoured again. If you relied on it while it was broken, perfdata really is dropped now.
  • ๐Ÿ”’ Elastic over https verifies certificates. Point ca at your self-signed certificate, or set verify mode = none to keep the old behaviour. On Elasticsearch 6.x or older, set event type, metrics type and nsclient log type explicitly โ€” the legacy _type parameter is no longer sent by default.
  • ๐Ÿ”’ Syslog targets: configured severities and templates now apply. Values set on a [/settings/syslog/client/targets/โ€ฆ] section were never read, so the built-in defaults always won. Review those sections for stale keys. Receivers whose parsing rules keyed on the old hostname-less datagram need adjusting โ€” records now arrive attributed to the agent’s host name instead of the tag. Syslog remains cleartext and unauthenticated: keep the path to the server on a trusted segment.
  • โฑ๏ธ Slow endpoints now fail instead of hanging. A submission to an unresponsive Icinga, NRDP, Graphite or Elastic endpoint gives up after timeout (default 30 s) โ€” raise it on that target if the endpoint is legitimately slower, or set timeout = 0 on an Icinga target if you depend on the old unbounded wait. Graphite’s timeout is now a budget for the whole submission, so a target that only completed by quietly taking longer will fail at the configured value.
  • ๐Ÿงน Graphite’s retry is no longer read. The module always made one attempt; the setting still appears in the reference (it is registered for all client modules centrally) but has no effect.
  • ๐Ÿ”’ tls version with a trailing + means “that version or later”. 1.2+ previously negotiated TLS 1.2 only; it now also permits TLS 1.3, and any is accepted as documented. This applies to the HTTP-based clients, the NRPE/NSCA clients and servers, and check_tcp. Pin an exact version (tls version = 1.2) if a peer misbehaves when TLS 1.3 is offered.
  • ๐Ÿ”’ CheckExternalScripts: re-run ext-scr install after upgrading so an argument lockdown lands on the setting the module actually reads. The default install is unaffected (arguments are off by default). Treat write access to any script path directory, and the ability to configure external-script commands, as equivalent to code execution as the service account.
  • ๐Ÿ”’ Filter expressions over 1024 characters or 64 nesting levels are rejected. Every normal configuration is far below both limits; only a pathologically large or deeply nested expression is refused.
  • ๐Ÿ”’ Credentials no longer appear in the trace log. password and token values are masked in the target dump at log level trace, across every outbound client module.

Full detail on the security items lives in Security notices; the operator actions are mirrored on Upgrading.

Download

You can download the new version from GitHub

// Michael Medin

0.18.0 Fixed some exotic passive checks, and security hardening

Most of 0.18.0 is about things that reported success while doing nothing. check_and_forward built its submission in a form no channel could read, check_nscp had counted zero crash reports since 0.4.2, and a module enabled by a fleet bundle was never loaded until the service restarted โ€” all three are fixed. Security reviews of the SMTP, NRPE, NRDP/NSCA and WEB modules landed alongside them, and the bundled OpenSSL moves to 3.5.8.

It also adds run_schedules for submitting a passive result without waiting out the interval, lets the MSI install your own TLS certificates, and gives real-time filters a way to prime their destination at startup.

โœจ Highlights

  • ๐Ÿ“ค check_and_forward submits again. The command ran the check, answered Message submitted and delivered nothing: the submission was built as a query message, which no channel can read. NSCA, NRDP, Graphite and every other client module were equally affected. It also gained channel, alias, destination and source. (#1452)
  • ๐Ÿ“… New: run_schedules. Run the configured schedules now instead of waiting out their interval, and submit the results on their normal channel โ€” nscp client --boot --query run_schedules, optionally --argument schedule=<alias>. Works over NRPE, REST and nscp test too. (#1450, #1452)
  • ๐Ÿ’ฅ check_nscp is a filter check, and its crash count works. It has read 0 crashes since 0.4.2 (it matched the extension txt against .txt), and since 0.6.10 there were no .txt reports to find. It now recognises .crash, reads the configured archive folder again, and exposes crashes, errors, uptime, crash_age, last_crash, last_error, version and date as filter keywords. (#1451)
  • ๐Ÿ›ก๏ธ Four security reviews โ€” SMTP, NRPE, NRDP/NSCA and WEB โ€” closed a set of defense-in-depth gaps: STARTTLS response injection, an unvalidated EHLO name, certificate verification that could not work on Windows, a metachar guard that ran before decoding, secrets in the trace log, and session tokens from a non-cryptographic generator.
  • ๐Ÿ” The bundled OpenSSL moves from 3.5.4 to 3.5.8 in the Windows builds, picking up four upstream security releases โ€” most relevantly CVE-2025-11187, a stack overflow parsing a hostile PKCS#12 file, reachable through check_certificate. (#1445)
  • ๐ŸชŸ The MSI can install your own TLS certificates. CERTIFICATE, CERTIFICATE_KEY and CERTIFICATE_CA place your files where every server module reads them, so the self-signed fallback is never generated. (#568)
  • โฑ๏ธ Real-time filters can prime their destination at startup. A new run on startup key submits the filter’s empty message once when the agent starts, so check_cache stops answering “Entry not found” after a restart. (#584)
  • ๐Ÿ“ก New ${address_ipv4} / ${address_ipv6*} hostname placeholders for every passive client, so a host can report itself by address instead of name. (#349)

๐Ÿ” Detailed changes

๐Ÿ“ค CheckHelpers โ€” check_and_forward delivers, and takes arguments properly

The command handed the raw QueryResponseMessage to the submission path, but channels parse a SubmitRequestMessage and the two are not wire compatible โ€” the payload sits in a different field, and field 2 of a submit message is the channel string. The channel received a message with zero payloads and cheerfully reported success. It now converts the query result into a submission first and checks the reply.

Option Meaning
channel Where to submit (default NSCA); target remains a synonym
alias Service description; defaults to the wrapped command’s name
destination Destination host for the submission
source Source host for the submission

target previously defaulted to the empty string, for which no handler exists, so even a correctly built message had nowhere to go.

๐Ÿ“… Scheduler โ€” run_schedules

run_schedules executes the schedules under [/settings/scheduler/schedules] immediately and submits each result on its own channel, target, source and alias with the same report filter, so the monitoring server cannot tell it from a timed run. The timers are untouched.

nscp client --boot --query run_schedules
nscp client --boot --query run_schedules --argument schedule=cpu

schedule= is repeatable and defaults to every schedule; an unknown alias is an error that names the ones you have. A schedule whose command is run_schedules is refused, and a reentrancy guard catches the indirect case (via check_timeout, for instance) โ€” previously that recursed until the agent died.

The caller’s identity is forwarded to the checks it runs, so REST and NRPE permissions apply to them; the scheduler’s own timed runs stay attributed to Scheduler.

๐Ÿ’ฅ CheckNSCP โ€” check_nscp rewritten as a filter check

Three independent bugs kept the crash count at zero: the extension comparison (txt vs the .txt the helper returns) has been false since 0.4.2; 0.6.10 replaced breakpad’s <guid>.dmp + .dmp.txt pair with a single <timestamp>.crash file, so even a fixed match found nothing; and 0.4.3 stopped reading [/settings/crash] archive folder, hardcoding the compile-time default. last_crash was never populated either โ€” the newest-file watermark started at the current time.

New filter keywords: crashes, errors, uptime, crash_age, last_crash, last_error, version, date. Thresholds accept duration units (crit=uptime < 5m, crit=crash_age < 7d), and a new max-unit option (default w) caps the largest unit rendered. Crash reports are a Windows concept; on Linux crashes is always 0.

โš™๏ธ Core โ€” reloads, channel verdicts and denied checks

  • A reload re-reads the included files before deciding which modules should run, so a module enabled in one since the last load is picked up. Only the includes are refreshed: clearing the whole store would discard configuration held in memory, which is exactly how nscp unit and nscp client set themselves up. One unreadable include no longer aborts the whole reload. (#1455)
  • Every channel’s verdict is reported for a channel list. All handlers were handed the same response buffer, so with channel=NSCA,GRAPHITE a failing NSCA was masked by a succeeding GRAPHITE.
  • A denied check is no longer submitted. The permission layer answers a denied query as a successful query carrying an UNKNOWN “Permission denied” payload; run_schedules and check_and_forward forwarded it, overwriting the last real result on the server while reporting success to the caller.
  • nscp client --query <cmd> no longer appends No module was specifiedโ€ฆ to every result.

๐Ÿ›ก๏ธ Security reviews

๐Ÿ”’ SMTPClient. Data pipelined across the STARTTLS handshake is refused (RFC 3207 ยง4) โ€” a prepared run of 2xx replies could otherwise walk the client through MAIL/RCPT/DATA and have it report an alert delivered while nothing was sent. The EHLO name is validated before connect, closing command injection on a relayed submission. Certificates are verified against a CA bundle through a new ca target setting and --ca argument (default ${ca-path}): the client previously used OpenSSL’s default verify paths only, which on Windows excludes the certificate store, so security=starttls failed against Gmail and M365 and operators simply turned verification off. EHLO capabilities are matched per reply line rather than by substring โ€” a greeting naming host starttls.example.com used to satisfy the STARTTLS lookup.

๐Ÿ”’ NRPE. The allow nasty characters = false guard now also runs on the decoded command and arguments; with a non-UTF-8 encoding, a multi-byte sequence could decode into a metacharacter that was never literally on the wire. A new expose version server setting (default true) lets the unauthenticated _NRPE_CHECK reply stop naming the exact build. nscp nrpe install reads the stored verify mode again instead of silently resetting it on every re-run.

๐Ÿ”’ NRDP / NSCA. A malformed <status></status> response no longer null-derefs the agent. The NRDP token and any proxy-URL credentials are redacted from the trace log โ€” including from the Target configuration: dump, which printed the raw settings map and defeated the redaction elsewhere (NSCA’s password leaked the same way). An https:// submission made through nscp client or REST with no verify mode now defaults to peer rather than trusting any certificate.

๐Ÿ”’ WEBServer. Session tokens and generated admin passwords come from OpenSSL’s CSPRNG with unbiased rejection sampling, and the server now fails closed if that RNG fails (HTTP 500 and a SECURITY: log line) rather than falling back to a weaker generator. Cookie lookups require a name boundary, so eviltoken no longer satisfies a lookup for token. Session validity and identity are read in one locked observation, closing an expiry race that could drop a request onto the anonymous grant. The web installer refuses an HTTPSโ†’HTTP redirect on the bundle download path.

๐Ÿ”’ OpenSSL 3.5.8. Windows builds only; Linux packages link the distribution’s OpenSSL. See Security notices.

๐Ÿ“ก Clients โ€” --source-host names the sender

--source-host / --sender-host were registered against the destination container, where the well-known host key is routed into the typed address field โ€” so naming a source host silently redirected the connection to it, and the sender the handler reads was never set. SMTPClient and NRDPClient had each worked around this with their own copies, which made the option ambiguous and therefore unusable on exactly the two modules most likely to need it.

๐Ÿ”ง Settings

  • settings --update --add-defaults --use-samples writes the sample objects; the flag was parsed and never read. --remove-defaults now enumerates samples too, making the two exact inverses. (#233)
  • A [/includes] entry naming a directory no longer breaks saving. The directory path reached the INI writer, and the resulting “Is a directory” error aborted the save โ€” so nscp settings --set and the web UI failed and the main file was never written. (#636)
  • New ${address_ipv4} and ${address_ipv6*} placeholders resolve in the hostname setting of NSCA, NSCANg, NRDP, Graphite, Syslog, Op5, Icinga, Elastic and Collectd clients. The address is the source address of the default route, falling back to the first non-loopback address the host name resolves to. (#349)

โฑ๏ธ Real-time filters โ€” run on startup

A new boolean filter key on the shared filter object, so it applies to CheckLogFile, CheckEventLog and the CheckSystem/CheckSystemUnix real-time filters. When true the filter submits its empty message with OK status once at startup, priming the destination. It is registered without a default on purpose: an absent key keeps the inherited value, while an explicit false overrides an inherited true. Delivery is retried across shortened waits while later modules (such as SimpleCache) are still loading. (#584)

๐ŸชŸ Windows installer โ€” install your own TLS certificates

Three new silent-install properties place your own files under the default names every server module reads, so the self-signed fallback is never generated:

Property Installed as
CERTIFICATE certificate.pem
CERTIFICATE_KEY certificate_key.pem
CERTIFICATE_CA ca.pem

The install fails if a named file is missing or is not PEM, or if a certificate is given with no key anywhere. CERTIFICATE_KEY also writes certificate key = โ€ฆ for the NRPE and WEB servers, so it is incompatible with ALLOW_CONFIGURATION=0; other servers need the setting added by hand. (#568)

๐Ÿ› Bug fixes

  • SMTP: the timeout error now says what happened in the client’s own words (timed out after 30s (the budget for the whole submission)) rather than reporting a platform error that blamed the connected party; a failed connect raises the exception callers actually catch; insecure-skip-verify is accepted over REST and no longer resets a configured target’s value on every submission; the reference no longer shows its default as N/A.
  • NRPE: the arguments-case rejection says “arguments” instead of “command”.
  • check_nscp: a crash report whose timestamp cannot be read still counts but takes no part in newest-wins, so crash_age no longer reports ~56 years.
  • Configuring with -DNSCP_BUILD_TESTS=OFF no longer aborts on the mongoose_wrapper_test target.

โš ๏ธ Upgrade notes

  • check_and_forward starts delivering, and no longer takes positional arguments. Anything that treated Message submitted as success will now see real channel failures. check_and_forward command=check_cpu warn=load>80 now fails to parse โ€” pass one arguments= per wrapped argument instead: check_and_forward command=check_cpu "arguments=warn=load>80". The positional form had to go because its parser also swallowed the CLI’s own --argument key=value tokens, which is what fed the wrapped command garbage.
  • check_nscp may start reporting CRITICAL. Its crash count works now, so an agent with an old report still in the archive folder will report a crash where it read 0. Threshold on crash_age ("crit=crash_age < 7d") if you only care about recent crashes, or clean the folder out. The message also loses its last crash: / last error: fragments โ€” put them back with an explicit detail-syntax if you match on the text.
  • A denied check now fails instead of submitting. If you restrict what a REST or NRPE identity may run, expect an error where a stale UNKNOWN previously appeared on the monitoring server.
  • NRDP over HTTPS verifies by default on the nscp client / REST path. Pass --verify none (or point --ca at the certificate) to keep submitting to a self-signed endpoint that way. Configured targets already defaulted to peer. ๐Ÿ”’
  • SMTP verifies the server certificate, against ${ca-path} by default. A target that relied on verification being effectively off needs ca pointed at the right bundle, ca = none for OpenSSL’s defaults, or insecure-skip-verify = true. ๐Ÿ”’
  • The SMTP timeout is now a budget for the whole submission, not a fresh deadline per operation โ€” a target that only completed by consuming several multiples of it will now give up.
  • --source-host no longer redirects the connection. If you used it to choose where to connect, use --host / --address.
  • settings --add-defaults --use-samples now writes samples. Drop the flag if you were relying on today’s sample-free output; --remove-defaults strips them again.
  • nscp client --query output loses its trailing No module was specifiedโ€ฆ line. Scripts that stripped it can stop.
  • Settings writes work again when [/includes] names a directory. If you removed a directory include to get saving working, you can put it back.
  • WEBServer: a script or module name beginning with - is rejected (rename it; interior dashes are fine), and POST /auth/logout now enforces allowed hosts. ๐Ÿ”’
  • NRPE: allow nasty characters = false now also inspects decoded input, so a request the guard was always meant to block may now be rejected. Set expose version = false to stop the _NRPE_CHECK ping naming your build. ๐Ÿ”’

Download

You can download the new version from GitHub

// Michael Medin

0.17.0 Windows server roles get their own checks, and check messages finally read like numbers

0.17.0 adds twelve new checks โ€” eight for IIS and Remote Desktop Services on Windows, four for the status pages of the common web servers โ€” and gives every filter check control over how it renders numbers, so 140.293GB/0.983TB can become 141.09GB/1006.85GB (or 141,09GB/1.006,85GB). Alongside that, the filter engine stops quietly doing the wrong thing: text-versus-number comparisons are numeric, fractional thresholds mean what they say, and an error inside a syntax template is reported instead of rendering a blank.

Highlights

  • Eight new Windows checks for IIS and Remote Desktop Services. The new CheckWindowsApps module covers IIS sites, application pools, worker processes and HTTP.sys request queues, plus RDS CAL licensing, session counts, per-session load and the Connection Broker counterset.
  • Four new web-server status checks. check_apache_status, check_nginx_status, check_phpfpm_status and check_tomcat_status read the vendors’ machine-readable status endpoints over HTTP(S), sharing check_http’s auth and TLS handling.
  • Check messages can be told how to render their numbers. Four new options โ€” decimals, byte-unit, decimal-separator, thousands-separator โ€” on every filter check and every real-time filter (#1428). Perfdata and thresholds are untouched.
  • Filter comparisons between text and a bare number are now numeric. filter=value > 90 no longer matches value=100 as false because “100” sorts before “90”, and 90 > value evaluates at all.
  • Fractional thresholds stop being truncated. count > 2.5 meant count > 3 and working_set > 1.5g meant 1g; both now mean what they say.
  • Host name placeholders resolve across the whole settings subsystem โ€” including attachment targets and [/includes] (#458) โ€” and are sanitized before they land in a local path ๐Ÿ”’.
  • Syslog submission works again after ten years. SyslogClient read its connection settings from the wrong place and sent nothing at all; a configured syslog target will start receiving traffic on upgrade.
  • Check-specific filter keywords that shadowed the generic summary keywords are renamed, with the old names kept as deprecated aliases.

Detailed changes

CheckWindowsApps โ€” a new module for Windows server roles

A new Windows-only module carrying IIS and Remote Desktop Services checks, built on the performance counter sets and enriched from WMI where the role’s provider is installed. The two roles share one module deliberately: every check module statically links Boost and the filter engine, so a role earns its own DLL only when it drags in a heavy or optional dependency (the way CheckMySQL carries libmariadb.dll).

Command Reports
check_iis_app_pools Per-pool state, uptime and recycles. CRITICAL by default when an auto-start pool is not running; a pool that has never started since boot surfaces as unknown rather than hiding.
check_iis_sites Per-site state, connections and uptime, plus requests_per_sec / bytes_per_sec behind averages=true. CRITICAL by default when an auto-start site is stopped.
check_iis_worker_processes Per-w3wp active and served requests, with the <pid>_<pool> instance name split into keywords. An empty set is OK โ€” idle pools spin their workers down.
check_iis_request_queues Per-queue length, rejections and age, defaulting to HTTP.sys’ 1000-request limit (warn > 800, critical > 1000).
check_rds_licenses One record per CAL key pack from Win32_TSLicenseKeyPack: total, issued and available licences. Warns at available < 10 and total > 0, critical at available = 0 and total > 0.
check_rds_sessions Active, inactive and total session counts, all three as perfdata. No default thresholds โ€” the interesting limits are per-farm.
check_rds_session_load One record per session (Console, Services, RDP-Tcp <n>) with CPU, working set and, on session hosts, RDP protocol bytes โ€” the per-user attribution check_process cannot give. sessions-only=true skips the session-0 aggregate.
check_rds_broker The Connection Broker counterset. Counter names vary between Windows Server versions, so the check enumerates whatever the counterset exposes and reports one record per counter instead of hard-coding names.

A host without the role gets a clean UNKNOWN naming the missing role, not a WMI or PDH error dump. The counter plumbing landed as a reusable gather helper that collects a set of English counter names for every instance of an object in one query, keeping the existing localized/English/index resolution fallback โ€” it was verified against live Swedish-localized counters.

CheckNet โ€” status-page checks for the common web servers

Command Endpoint Keywords
check_apache_status mod_status (?auto appended automatically) workers, requests/s, scoreboard
check_nginx_status stub_status active/reading/writing/waiting, cumulative accepts/handled/requests, derived dropped count
check_phpfpm_status FPM status page processes, listen queue, max_children_reached, slow requests; warns by default when requests queue up
check_tomcat_status manager status?XML=true (appended automatically) per-connector thread pool, request/error counters, JVM heap; defaults fire at 75%/90% pool usage

All four share check_http’s connection handling โ€” Basic auth, TLS version / verify / CA, timeout โ€” and go CRITICAL by default when the endpoint is unreachable, answers non-2xx, or serves something that is not the expected status format. Numeric parsing pins the classic locale, so a host with a decimal comma no longer truncates ReqPerSec at the decimal point.

Filter messages โ€” configurable number rendering

check_drivesize reported 140.293GB/0.983TB used: two units and six decimals in one line, with no way to change either (#1428). Every filter check now takes four options, and real-time filters take the same values as settings keys (decimals, byte unit, decimal separator, thousands separator), inheritable from the default template.

Option Effect
decimals Exactly N decimals. Default -1 keeps the historical “up to three, trailing zeros stripped”. Capped at 15.
byte-unit Pin every byte value to one unit, Bโ€ฆEB.
decimal-separator Radix character โ€” , for the European rendering.
thousands-separator Digit grouping for the integer part.
check_drivesize drive=/ show-all=true decimals=2 byte-unit=GB
OK /: 141.09GB/1006.85GB used

check_drivesize drive=/ show-all=true decimals=2 byte-unit=GB \
  decimal-separator=, thousands-separator=.
OK /: 141,09GB/1.006,85GB used

The format lives on the evaluation context, so it reaches the message only: performance data is built from the raw values and keeps its full precision and its . radix, and so does every number the filter grammar parses out of a threshold โ€” warning=used>1.5g means the same thing with a decimal comma in force.

Three defects in the byte formatter came out of this work:

  • format_bytes(used, 'gb') rendered 1.27055e-10, because the unit comparison was case sensitive against an uppercase table. Units are now case insensitive everywhere.
  • A unit that matched nothing fell out of the comparison having divided seven times, rendering value/1024^7. An unknown unit is now reported โ€” Filter processing failed: format_bytes failed: Unknown byte unit: ZB โ€” and the same check applies inside real-time filters.
  • format_bytes(value, '') failed to parse at all; the empty string literal is now accepted.

The filter/where engine โ€” comparisons that mean what they say

  • Text keyword versus bare number is numeric. A string-typed keyword compared against an unquoted number used to order lexically, or โ€” with the operands reversed โ€” fail to evaluate. Both sides now compare as numbers. This covers value/warn/crit/min/max (filter_perf, render_perf), speed (check_network), string_value (check_registry_value) and column() (check_logfile). A value that is not a number never matches; the check logs one warning naming it and stays a certain non-match, not UNKNOWN. Quoted literals keep the lexical comparison, as do like, regexp, in, keyword-specific converters (state = 'running', age > 30m) and the = 'unknown' / = 'never' sentinels.
  • Fractional numbers survive. count > 2.5 used to be rounded into the counter’s integer domain, and unit literals lost their fraction entirely (working_set > 1.5g meant 1g, uptime < 2.5h meant 2h).
  • filter_perf/render_perf/xform_perf: max and min were swapped. max read the perf-data minimum bound and min the maximum; they now read the bounds they name.
  • Template errors are reported. A function that failed inside detail-syntax or top-syntax left the placeholder empty and said nothing; the check now returns UNKNOWN with Filter processing failed: โ€ฆ.
  • perf-config’s unit: converts instead of relabelling. On byte series that do not auto-scale, unit:KB used to change the label only, shipping =1536KB for 1536 bytes. The value and the warn/crit bounds now convert. An unrecognised unit leaves the value alone rather than dividing it by 1024โท.

Filter keywords โ€” the clash with the generic summary keywords is resolved

A handful of checks registered a keyword named status, count or total โ€” the same names as the built-in summary keywords. The check-specific value won in filter/warning/critical and detail-syntax, while top-syntax and the reference documentation showed the generic one. Each now has a distinct name:

Check Old New
check_cpu, check_cpu_utilization total usage
check_battery status battery_status
check_network status, total link_status, throughput
check_os_updates count updates
check_patch_age count patches
check_pending_reboot count signals
check_printjobs status job_status
check_printqueue status printer_status
check_installed_software (Linux) status package_status
check_activation status activation_status
check_docker status container_status
check_connections count, total connections, total_connections
check_dns count records
check_http status status_message
check_shadowcopy count copies
check_disk_health total size

The old names remain as undocumented deprecated aliases with unchanged behaviour, so check_cpu "warn=total > 80" still works.

Settings โ€” host name placeholders, and where they may land

${host}, ${hostname}, ${hostname_lc}, ${hostname_uc} and ${domain} now resolve in attachment target paths and in [/includes], not only in settings urls and the url an attachment is fetched from (#458). An unknown ${...} token in a path is not an error โ€” it resolves to the installation directory โ€” so a configuration like [/attachments] ${shared-path}/${host}.ini = โ€ฆ never failed, it quietly wrote one file with the installation directory in its name.

๐Ÿ”’ Because the host name is not fully under the operator’s control (DHCP, or any local privileged process can set it), a value substituted into a path is reduced to the characters a legal RFC-952 host name can contain: anything else becomes _, and a dots-only value becomes _. Settings urls and the submit clients’ host name specs are unaffected. See Security notices.

nscp settings --migrate-to (and the REST migrate) now keeps a placeholder you pass it as-is in boot.ini while migrating into the expanded per-host file, the way --switch already did, so the template survives on a fleet-managed machine.

Clients โ€” submission paths that were quietly dead

  • Syslog. SyslogClient read its connection settings from the sender rather than the target, so address, port, facility, severity and templates were all ignored: the agent logged Undefined facility: and sent nothing. Broken since 0.4.3 (2015). CheckMKClient had the same defect on its query path.
  • SMTP. The sender’s host name was read from the wrong place, so it was always empty and the EHLO fell back to localhost. Set ehlo-hostname on the target if your mail server applies HELO/EHLO policy.
  • Short command names. A client command shorter than eight characters โ€” cpu, run โ€” answered Exception processing command line: basic_string::substr โ€ฆ instead of running, in every module built on the shared client machinery (NRPE, NSCA, NRDP, Graphite, โ€ฆ).

CheckSystem โ€” check_pending_reboot says since when

The CBS and Windows Update reboot keys exist only while their reboot is queued, so their last-write time is when the signal appeared. Two new keywords follow check_registry’s naming: written (type_date, plus written_s) and age, duration-typed so warning=pending = 1 and age > 7d reads as seven days. The default message gains (pending since <time>) when the time is known (#1415). The file-rename, computer-rename and domain-join signals carry no timestamp, so both keywords are optional: they render as unknown, compare false against every number and emit no perfdata rather than reporting a misleading value.

Data collection โ€” one bad field no longer sinks the cycle

  • WMI. Win32_Processor.LoadPercentage is occasionally NULL, and row::get_int had no case for it: the type-mismatch exception escaped half-way through the row and the collector threw away the entire cycle’s clock speeds and core counts (#1391). Optional fields can now opt into boost::none, mandatory ones fail with a clear <col> is NULL instead of localized COM text, and check_cpu_frequency renders a missing sample as no load sample rather than a fabricated 0.
  • PDH. MaxQueueItemAge on an idle, freshly started queue returns PDH_CALC_NEGATIVE_DENOMINATOR, which failed a whole gather even when the caller passed ignore_errors โ€” making check_iis_request_queues misreport the object as missing. With ignore_errors the counter is now skipped for that tick (#642, #906); the background collector and every single-counter check still throw, so they hear about an uncomputable counter instead of silently reading a default.

Windows installer and file layout

A round of fixes to the modern (ProgramData) layout introduced in 0.16.2: upgrading an enrolled host resolves every path token instead of failing; ReadLayout gets the install folder before directories resolve; CURRENT_LAYOUT is set through the public property setter; an upgrade of a modern host no longer re-creates nsclient.ini in Program Files; a %ProgramData% that cannot be resolved fails outright instead of half-applying the layout; migrated files get the destination’s ACL rather than the one they came with; resetting a renamed tree to inherited strips the explicit ACEs; and --migrate-layout legacy migrates to legacy instead of silently to modern.

Bug fixes

  • nscp settings --show --path โ€ฆ without a --key used to print nothing and exit 0; it now reports Invalid command line please use --path and --key with show and exits non-zero.
  • The settings diff behind the REST diff endpoint kept listing an edit for the lifetime of the process after it had been written, reporting a modified entry whose old value equalled its new one.
  • Only the pending markers the backend confirms are dropped on save, and staged deletions are masked in has_key.
  • Boolean option defaults render as true/false in the generated reference instead of garbage.

Documentation

Options shared by every filter check (filter, warning, top-syntax, โ€ฆ) and the generic filter keywords are now single-sourced: they fold out of each command’s reference page into one shared page, so a command’s documentation shows only what is specific to it. Runtime-stubbed Windows-only checks are marked as Windows only.

Upgrade notes

  • Syslog starts delivering. If you have a syslog target configured, check it still points where you want before upgrading โ€” it has not been delivering, and it will now. The same applies to SMTP targets, which will start announcing this host in EHLO instead of localhost.
  • Host name placeholders in paths now resolve. Check any ${host}, ${hostname} or ${domain} under [/attachments] or [/includes] and remove workarounds โ€” such a file lands somewhere new after upgrade. ๐Ÿ”’ The value is sanitized when it lands in a local path. Configurations without a host name placeholder are unaffected.
  • Number rendering is opt-in, but it is all-or-nothing per check. Leave all four options unset and messages are byte-for-byte unchanged. Set any of them and plain float keywords move onto the number format too: with decimals unset they render with up to three decimals instead of the legacy 6-significant-digit form (2.71094 โ†’ 2.711), and large values stop rendering scientific. A pipeline that matches float text in the message may need its pattern relaxed.
  • An unknown unit in format_bytes() now returns UNKNOWN instead of a quietly wrong number. A syntax string with a typo’d unit will fail until the unit is fixed.
  • perf-config unit: on plain byte series changes the metric’s magnitude. A dashboard that compensated for the old mislabelling will see the metric drop by the unit ratio; a graph flat at a near-zero value because of a misspelled unit: will jump to its real magnitude.
  • Filter comparisons against a bare number are numeric. Review any filter that deliberately relied on text ordering โ€” quote the number to keep the old behaviour.
  • Fractional thresholds change meaning. Whole-number thresholds are unchanged; expressions that already used a decimal point can behave differently.
  • max and min in filter_perf/render_perf/xform_perf were swapped. A filter that compensated needs the two names exchanged back.
  • Renamed filter keywords keep working through deprecated aliases, but three default perfdata keys change because the default perf-config names the renamed keyword: check_cpu_utilization (Linux) cpu_total โ†’ cpu_usage, check_patch_age patch_count โ†’ patch_patches, check_pending_reboot reboot_count โ†’ reboot_signals. Pass your own perf-config=extra(...) with the old name to keep the old key. check_os_updates’ default output now reports the actual number of updates instead of the matched-row count.
  • check_pending_reboot’s default message gains a suffix โ€” Reboot required: Windows Update (pending since 2026-08-16 09:41:12). Notification pipelines matching the exact message text need their pattern relaxed.
  • nscp settings --show without --key now fails. Scripts relying on the silent success need the missing --key added.

Download

You can download the new version from GitHub

// Michael Medin

0.16.4 Security release: request-smuggling fixes in the bundled web server

0.16.4 upgrades the Cesanta Mongoose web server bundled in the Windows builds to 7.23, closing two critical HTTP request-smuggling vulnerabilities in its HTTP parser. If NSClient++’s web server is reachable through a reverse proxy or WAF, upgrade promptly.

Highlights

  • ๐Ÿ”’ Bundled Mongoose upgraded from 7.20 to 7.23. Fixes two critical (CVSS 9.1) HTTP request-smuggling vulnerabilities, CVE-2026-73256 and CVE-2026-73257, fixed upstream in Mongoose 7.22.
  • Windows builds only. The Windows WEBServer module (REST API and web UI) uses the Mongoose backend; the Linux DEB/RPM packages build on Boost.Beast and never contained the vulnerable code.
  • Exploitable behind an intermediary. Both flaws let an unauthenticated attacker smuggle requests past a reverse proxy, WAF or load balancer in front of NSClient++ โ€” bypassing proxy-level ACLs or injecting into other clients’ reused connections. Direct client โ†’ NSClient++ deployments have no front end to desynchronize, and NSClient++’s own authentication is still enforced per request either way.

Detailed changes

WEBServer โ€” bundled Mongoose upgraded to 7.23 (security)

Mongoose versions before 7.22 mis-parse HTTP message framing in two ways:

CVE Flaw
CVE-2026-73256 Broken HTTP/1.0 detection in http_cb() โ€” a request combining Transfer-Encoding: chunked with conflicting HTTP/1.0 framing is parsed with different message boundaries than an HTTP/1.0 reverse proxy sees.
CVE-2026-73257 Requests carrying both Content-Length and Transfer-Encoding: chunked are accepted instead of rejected, enabling CL.TE desynchronization against a Content-Length-preferring front end.

All build pipelines now pin Mongoose 7.23 (the latest release, which also carries further upstream TLS and TCP/IP hardening): the Windows CI workflows, the Linux docker scenario images that fall back to the Mongoose backend (minimal, no-openssl), and the developer build instructions. The full advisory record is on the Security notices page.

Upgrade notes

  • ๐Ÿ”’ Upgrade Windows installs, promptly if behind a reverse proxy/WAF: the request-smuggling CVEs only matter when an intermediary in front of NSClient++ frames the HTTP stream differently than the built-in web server. No configuration change is needed โ€” this is a drop-in upgrade.
  • Linux packages are unaffected (Boost.Beast web backend, no Mongoose), as are installs with the WEBServer module disabled.

Download

You can download the new version from GitHub

// Michael Medin

0.16.3 check_nt works with the real nagios-plugins client again

0.16.3 is a small bugfix release: it restores compatibility between the legacy check_nt server (NSClientServer) and the real nagios-plugins check_nt client โ€” broken since 0.12.2 โ€” and pins the fix with an integration suite that drives the genuine client against NSClient++ in CI. It also reorganises the reference documentation for readability.

Highlights

  • check_nt requests without a trailing newline are answered again. Buffer-cap hardening in 0.12.2 made the server wait for a newline terminator, but the real nagios-plugins check_nt sends <password>&<cmd>&<args> with no terminator โ€” so every one of its requests has hung until the client’s socket timeout (No data was received from host!) in every release since (#1421).
  • The fix is pinned by a real-client integration suite. CI now compiles check_nt from the official nagios-plugins 2.5 release and drives it against the server, covering the protocol commands, password enforcement and the allow command gating (#1421).
  • Securing check_nt is now documented. New guidance covers the password, allowed hosts and the allow setting that limits which commands the legacy endpoint will answer.
  • Reference docs reorganised. Queries are listed first and every command carries an OS column with platform logos, so it is clear at a glance what exists on Windows vs Linux.

Detailed changes

check_nt โ€” compatibility with the real nagios-plugins client restored

The buffer-cap hardening that shipped in 0.12.2 made the legacy check_nt server wait for a newline terminator before parsing a request. The real nagios-plugins check_nt sends its request with no terminator and waits for the reply, so every request from it has hung until the client’s own socket timeout in every release since. End-of-read is once again end-of-request, while both halves of the hardening are kept: the 4 KiB request cap, and the newline path (which consumes the terminator and leaves pipelined bytes intact) for line-oriented clients.

The behaviour is now pinned at two levels: unit tests on the request parser (no-terminator format, newline path, empty chunk, oversized-line cap), and an integration suite that compiles check_nt from the official nagios-plugins 2.5 tarball in a container and runs it against nscp test โ€” covering CLIENTVERSION, UPTIME, CPULOAD, MEMUSE, USEDDISKSPACE and PROCSTATE, wrong-password handling, and the allow command gating including its fail-closed behaviour (#1421).

Documentation

  • New guidance on securing the legacy check_nt (NSClientServer) endpoint: set a password, restrict allowed hosts, and use the allow setting to limit which commands it answers.
  • The reference docs put queries first and add an OS column with platform logos to every command.

Packaging

  • Automatic Chocolatey publishing on release is disabled while the package onboarding with chocolatey.org is being sorted out (#1422). The workflow can still be run manually; the MSI, DEB, RPM and ZIP packages are unaffected.

Upgrade notes

  • check_nt clients that never sent a trailing newline get answers again. If you scripted around the hang (client-side timeouts, retries, or switching clients), those workarounds are no longer needed. No configuration change is required; the default install is unaffected unless NSClientServer is enabled.
  • Chocolatey: NSClient++ is not yet available from chocolatey.org; use the MSI from the release page for Windows installs.

Download

You can download the new version from GitHub

// Michael Medin

0.16.2 A locked-down modern Windows layout, new security and system checks, and safer settings and roles

This release introduces an opt-in modern Windows file layout that separates and locks down the agent’s writable state, adds a batch of Windows security and system checks, and hardens two areas of the WEB/settings surface. Default installs are unaffected until you opt in to the new layout.

Highlights

  • Modern, locked-down Windows file layout (opt-in). Configuration, the fleet identity, and writable state can now live in a dedicated %ProgramData% folder that is restricted to SYSTEM and Administrators, instead of sitting under Program Files. Switch with nscp settings --migrate-layout modern (or the MSI LAYOUT property); the classic layout remains the default and is untouched.
  • Writable state and a fleet folder are first-class. A ${fleet-folder} token and dedicated writable-state directories keep fleet/enrollment material and mutable state out of the package/program directories, with local overrides now visible in diagnostics. On Linux packages the writable state directories are created and migrated automatically.
  • New CheckSecurity checks. check_activation (Windows licensing state), check_file_security (file owner / DACL hardening), and check_firewall_rules (assert on individual firewall rules).
  • New CheckSystem checks. check_w32time (Windows Time service health) and check_printjobs (per-job print detail), plus reporting the printer device behind each queue.
  • Sensitive settings values are redacted on read. The REST settings read endpoints and the nscp settings --list / --show CLI now return *** for keys registered sensitive, matching the diff endpoint. Reported by @yagust.
  • The legacy WEB permission is flagged and no longer seeded by default. It unlocks deprecated query-dispatch endpoints that can run any registered command; fresh installs no longer create the role and a SECURITY warning is logged for any role that grants it. Reported by @yagust.
  • A documented upgrade path. New Upgrading and Security notices docs pages collect per-version operator actions and security-relevant changes in one place (#1410).

Detailed changes

Modern Windows file layout

The agent can now run in a “modern” layout where its configuration, fleet identity, and writable state live in a dedicated, ACL-restricted %ProgramData% folder rather than under %ProgramFiles%. The layout is recorded in boot.ini and resolved through a single shared path-token table used by both the service and the bundled clients, so ${shared-path}, ${log-path}, ${fleet-folder} and friends resolve consistently everywhere.

Migration is available both from the CLI (nscp settings --migrate-layout modern, with --dry-run) and from the MSI (via a LAYOUT property). The migration is defensive: it refuses to move into a populated destination on the first switch, locks the destination down before writing any secret into it, moves across volumes rather than failing, and keeps shipped program content out of the redirected shared path.

Change Effect
--migrate-layout modern / legacy Move an existing install between layouts (dry-run supported).
MSI LAYOUT property Install/upgrade directly into a chosen layout.
${fleet-folder} token Addresses the fleet/enrollment folder in the active layout.
Locked-down shared folder Restricted to SYSTEM + Administrators; ownership taken, not just the DACL.

New and updated checks

  • CheckSecurity: check_activation, check_file_security (owner + DACL hardening), and check_firewall_rules (individual rules). Corrected three check_file_security verdict paths and kept expect= assertions visible through a firewall filter.
  • CheckSystem: check_w32time for the Windows Time service, check_printjobs for per-job print detail, and the printer device is now reported behind each queue. Duration keywords keep their -1 sentinel and last_sync_age is treated as a duration.
  • CheckDocker: survives containers removed mid-check and counts image disk correctly.
  • CheckMySQL: plugin-dir / socket / defaults-file are settings-only.

Security & hardening

  • Settings redaction (reported by @yagust): values for keys registered sensitive are returned as *** on the settings read paths (REST GET /api/v2/settings/... and /descriptions, and the --list / --show CLI), matching the diff endpoint. Internal reads a module makes of its own configuration are unaffected. This is defense-in-depth, not an authorization boundary โ€” the plaintext still lives in nsclient.ini. The web admin edit dialog now writes only changed fields so the mask cannot overwrite a stored secret.
  • Legacy WEB permission (reported by @yagust): the legacy grant unlocks the deprecated /query.pb and /query/{name} endpoints, which dispatch through the same command registry as /api/v2/queries. The built-in legacy role is no longer seeded on fresh installs, any role whose grant includes the legacy token now logs a SECURITY warning at startup (and from nscp web add-role / add-user), and the capability is documented in the securing guide.

Installer & packaging fixes

  • Repaired a self-initialised member and a clobbered boot.ini; stopped stamping [layout] into every boot.ini.
  • Remove the fleet identity on uninstall of a modern install; restore the config backup into the layout’s shared folder; honour boot.ini’s [paths] shared-path.
  • Linux packages create and migrate writable-state directories and keep them out of the package directory; adopt_owner handles root-written enrollment material and is symlink-safe.

Documentation

  • New Upgrading page collecting per-release operator actions (closes #1410), and a Security notices page tracking advisories and hardening changes.
  • Documented the Windows and Linux file layouts and the MSI LAYOUT property.

Upgrade notes

  • The modern layout is opt-in; the default install is unaffected. Switch deliberately with nscp settings --migrate-layout modern (try --dry-run first) or the MSI LAYOUT property. Run the CLI migration from an elevated prompt โ€” the destination is locked to SYSTEM/Administrators.
  • ๐Ÿ”’ Sensitive settings values now read back as ***. Tooling that read a secret out of GET /api/v2/settings/... will now receive *** for keys registered sensitive. No configuration change is required.
  • ๐Ÿ”’ The legacy WEB role is no longer seeded on fresh installs and any role granting the legacy permission logs a SECURITY warning. Existing installs keep their role and are unaffected; only grant legacy to trusted legacy systems.
  • Linux writable-state migration is automatic. Packages create and migrate the writable state directories on upgrade; no action required.

Download

You can download the new version from GitHub

// Michael Medin

0.15.0 SQL Server monitoring and seventeen new checks

This release adds a new CheckMSSQL module for monitoring Microsoft SQL Server, a large batch of new Windows checks covering disks, security hygiene and patch state, richer keywords across many existing checks, and fixes a long-standing class of collector stalls caused by slow WMI providers.

โœจ Highlights

  • ๐Ÿ—„๏ธ New CheckMSSQL module. Five new commands monitor Microsoft SQL Server over ODBC: connectivity/health, arbitrary T-SQL queries, database state and log usage, backup age and SQL Agent jobs. Windows integrated authentication by default, with optional SQL authentication.
  • ๐Ÿ†• Twelve more new check commands. Disk writability (check_disk_write), UNC share free space (check_uncpath), Storage Spaces (check_storagepool), VSS snapshots (check_shadowcopy), SMB shares (check_share), Microsoft Defender (check_defender), local account hygiene (check_local_accounts), group membership drift (check_group_members), pending reboot (check_pending_reboot), hotfix age (check_patch_age), print queues (check_printqueue) and paging I/O (check_swap_io).
  • โš™๏ธ The system collector no longer freezes on slow WMI providers. Slow every-12-second collections (network, temperature, CPU frequency, battery, OS updates) now run on their own thread, so a blocking WMI query no longer stretches check_cpu time windows or drops samples (#1378).
  • ๐Ÿ“ƒ Multi-line check output. The new list-separator option on every filter-based check lets long results render one item per line, which Nagios-compatible frontends show as summary + long output (#1370).
  • ๐Ÿ”ฅ check_firewall now reports the effective, group-policy-aware state. A firewall enabled or disabled through group policy previously reported its pre-policy local state (#1351).
  • โฑ๏ธ Per-disk I/O latency. check_disk_io and check_disk_health gain read_latency, write_latency and total_latency keywords in milliseconds, on both Windows and Linux (#1369).
  • ๐Ÿ› Fixed disable = cpu_frequency silently stalling check_cpu. Disabling CPU frequency collection also disabled CPU load sampling (#1368).
  • ๐Ÿง Linux packages now ship executable scripts. Bundled scripts lost their execute bit when installed by DEB/RPM packages. Thanks to Fabio Fantoni for this and for REUSE/SPDX compliance fixes.

๐Ÿ” Detailed changes

๐Ÿ—„๏ธ CheckMSSQL โ€” new module for monitoring Microsoft SQL Server

A new Windows module connecting over ODBC with Windows integrated authentication by default and optional SQL authentication (password stored as a masked settings key). The ODBC driver is auto-detected, preferring the newest “ODBC Driver NN for SQL Server” and falling back to the legacy “SQL Server” driver; on modern drivers TrustServerCertificate=yes is applied by default (overridable via trust-cert/encrypt). Login and query timeouts keep checks from ever hanging the agent, and unreachable servers report UNKNOWN with the full ODBC diagnostic chain.

Command Purpose
check_mssql Connectivity and health: version, patch level, edition, uptime with time-unit thresholds
check_mssql_query Arbitrary T-SQL with returned columns exposed as filter keywords and perfdata
check_mssql_databases Database state, recovery model and sizes, plus log usage from DBCC SQLPERF(LOGSPACE)
check_mssql_backup Age of last full/diff/log backup from msdb; never-backed-up reported as -1 and critical by default
check_mssql_jobs SQL Agent job outcomes, duration and in-flight runs (is_running)

check_mssql_backup excludes COPY_ONLY and snapshot backups by default so an ad-hoc dev backup or a VSS agent cannot mask a failing backup job (include-copy-only / include-snapshot opt back in). A new end-to-end scenario, Monitoring a SQL Server host, combines the module with service, disk, memory, PDH and event log checks and documents a low-privilege monitoring login.

check_mssql_backup "critical=full_age > 26h or full_age = -1" "warn=log_age > 2h"

๐Ÿ’พ CheckDisk โ€” writability probes, UNC paths, Storage Spaces, VSS and SMB shares

Command Purpose
check_disk_write Verify a disk is actually writable: exclusive-create a probe file, write, read back, delete. Never touches a file it did not create; probe size capped at 1M
check_uncpath Free space on a UNC path (server share), with optional alternate credentials
check_storagepool Storage Spaces pool health and capacity
check_shadowcopy VSS snapshot recency, count and shadow-storage usage per volume
check_share List SMB shares or verify that specific required shares exist

Existing disk checks were extended as well:

  • check_disk_io and check_disk_health expose average per-I/O latency (read_latency, write_latency, total_latency, unit ms) with perfdata and metrics (#1369). On Windows the values are computed from raw PERF_AVERAGE_TIMER counters (the formatted WMI class truncates realistic latencies to 0); on Linux from /proc/diskstats. Thresholds like "warn=total_latency > 20" "crit=total_latency > 50" work regardless of workload shape.
  • check_drivesize gains require (alias mandatory-drives): the check goes CRITICAL if any listed drive is missing, even when scanning wildcards.
  • check_drivesize and check_disk_health can report physical-disk device state (health and operational status).
  • check_files gains aggregate file-size metrics and a folder count.

๐Ÿ›ก๏ธ CheckSecurity โ€” Defender, local accounts and group membership

Command Purpose
check_defender Microsoft Defender status: signature/scan age, real-time and tamper protection, engine/signature versions
check_local_accounts Local account hygiene: enabled/disabled, locked, password-required/expires, built-in admin/guest
check_group_members Local group membership (default Administrators) with alerting on members not on an expected allow-list

๐Ÿ–ฅ๏ธ CheckSystem โ€” patch state, reboot state, print queues and paging I/O

Command Purpose
check_pending_reboot Whether the system is waiting for a reboot, aggregating servicing, Windows Update, file-rename, computer-rename and domain-join signals
check_patch_age Installed-hotfix hygiene: time since the newest hotfix and presence of specific required hotfixes
check_printqueue Print queues: queue depth, oldest-job age, offline and error states per printer
check_swap_io System paging (swap) I/O rates: pages/bytes paged in and out per second

๐Ÿ“ˆ check_process โ€” background CPU sampling, owners and more memory keywords

check_process delta=true previously sampled inside the check, slept one second and sampled again โ€” stalling every query by a second. CPU deltas are now published by an opt-in background collector (process cpu setting, mirroring process history) that diffs the process table once a second; the check overlays a rolling per-PID CPU% onto a normal no-sleep enumeration. With the collector off, delta=true fails fast with UNKNOWN naming the setting instead of reporting misleading values, and memory/handle fields now keep their real absolute values in delta mode.

Other process-check additions: process owner resolution (with user filtering), an rss alias for working set, thread count, working set and page file percentages, peak memory keywords and system-wide thread/memory totals. Also fixed: the time keyword always reported 0 unless delta sampling was on.

โž• More keywords and options for existing checks

Check Addition
check_network Per-interface packet rates, errors and discards (packets_in, packets_out, …) with perfdata and metrics; NIC team membership (team, team_status) and WMI source keywords
check_service summary option emitting aggregate state counts (running_services, stopped_services, paused_services, pending_services, service_count) for dashboard rollups
check_os_version CPU architecture, Windows build revision and inventory-only BIOS fields (serial, version, manufacturer); fixed version detection for Windows 10/11 and Vista/Server 2008
check_os_updates Support for Defender definition updates and update rollups
check_cpu_frequency Socket information and load percentage
check_tasksched Next run time and missed-run tracking, task URI and hidden properties, default perfdata for task state and missed-run counters
check_eventlog User SID retrieval and filtering; more efficient bookmark handling (plus a bookmark bug fix)
check_pdh Built-in memory_pages_sec counter (\Memory\Pages/sec); more robust resolution of localized counter names

โš™๏ธ CheckSystem collector โ€” no more stalls from slow WMI providers (#1378)

The background collector ran network, temperature, CPU frequency, battery and OS update collection on the same 1 Hz thread as CPU/memory/PDH sampling. The network collection queries Win32_PerfRawData_Tcpip_* via WMI with no timeout; when the WMI Performance Adapter service restarts (roughly every 16 minutes on an idle server) that query blocks for 21โ€“24 seconds, freezing the whole collector โ€” stretching check_cpu time windows and dropping samples. The five slow collections now run on their own thread, so a slow provider costs one stale cycle for that metric instead of a frozen collector.

The follow-up hardening fixed a subtle shared-state bug: CheckSystem, CheckEventLog and CheckLogFile all created the same named shutdown event, so stopping or reloading any one of them silently killed the others’ background threads โ€” and the name let any co-resident process signal it and disable monitoring from outside. All three now use unnamed, per-instance events with proper cleanup, and a transient COM initialization failure at boot now retries instead of permanently disabling collection.

๐Ÿ“ƒ Filters and output โ€” multi-line lists and REST-safe booleans

  • list-separator (#1370): every filter-based check now accepts a separator for %(list), %(ok_list), %(warn_list), %(crit_list), %(problem_list) and %(detail_list), with \n, \r, \t and \\ escapes; real-time filters get a matching list separator settings key. The decoded separator is also exposed to templates as %(sep) so the line can break before the first item:
check_users "top-syntax=%(status): %(count) user(s) logged on:%(sep)%(list)" "detail-syntax=%(user) [%(state)]" "list-separator=\n"
OK: 7 user(s) logged on:
administrator [active]
user1 [active]

The default (,) is unchanged and templates pass through byte-for-byte. - Valued booleans on common options. debug, show-all and escape-html rejected the x=true form used by REST (answering with usage text instead of running); they now accept x=true/x=false while the bare CLI form keeps working. - %(problem_list) leak fixed. Real-time filters reuse one filter instance; %(problem_list) kept accumulating items from every previous event batch.

๐Ÿ”ฅ check_firewall โ€” effective, group-policy-aware state (#1351)

check_firewall read only the local policy store, so a firewall configured through local or AD group policy reported its pre-policy state โ€” a GP-disabled firewall showed as enabled and vice versa. The group-policy resultant values (EnableFirewall, default inbound/outbound actions) are now overlaid on the local store, matching Get-NetFirewallProfile -PolicyStore ActiveStore, including the legacy pre-Vista “Protect all network connections” StandardProfile key. A new policy keyword exposes whether a profile’s settings come from local or group policy.

๐Ÿ› Bug fixes

  • disable = cpu_frequency in the CheckSystem collector also disabled CPU load sampling, silently stalling check_cpu (#1368).
  • check_process time keyword always reported 0 without delta sampling.
  • A CheckEventLog bookmark bug could skew incremental event log scanning.

๐Ÿ“ฆ Packaging and licensing

  • Linux DEB/RPM packages now install the bundled scripts with their execute permission, and check_ok.sh gained its missing shebang, so they can be invoked directly as external-script commands (thanks Fabio Fantoni).
  • REUSE/SPDX compliance: third-party attributions for bundled CMake modules and binaries are now correctly declared, and the SBOM no longer misattributes them (thanks Fabio Fantoni).

โš ๏ธ Upgrade notes

  • check_process delta=true behaviour changed: it now requires the new process cpu collector setting to be enabled and returns UNKNOWN (naming the setting) when it is off, instead of sleeping one second inside the check. With the collector on, memory and handle fields report absolute values in delta mode rather than 1-second differences. Default installs (not using delta=true) are unaffected.
  • CheckMSSQL is a new optional module; it is not loaded by default. Enable it and see the new Monitoring a SQL Server host scenario in the docs.
  • All other changes are additive; existing configurations render byte-for-byte as before.

Download

You can download the new version from GitHub

// Michael Medin

0.14.1 Host security posture, JSON-aware HTTP checks, and a clearer licence

This release adds a brand-new CheckSecurity module for monitoring a host’s security posture โ€” certificates, firewall, antivirus, BitLocker, Secure Boot, NLA and logged-on users โ€” and teaches check_http to assert on values inside a JSON response body. It also fixes boolean check arguments over REST, tidies up process aggregation and module activation, relicenses the project under a clear dual licence, and reworks the documentation to handle Windows and Linux side by side.

Highlights

  • New CheckSecurity module. Seven new checks for host security posture: check_certificate, check_firewall, check_antivirus, check_bitlocker, check_secureboot, check_nla and check_users. check_certificate and check_users run everywhere; the rest are Windows-only. (#1339)
  • check_http can assert on JSON responses. New json-path=alias:path options extract values from a JSON body into filter keywords you can threshold on and emit as perfdata. (#1341)
  • CheckNet queries now emit performance data by default, so check_http, check_tcp and friends graph out of the box without an explicit perf syntax. (#1341)
  • Boolean check arguments accept values, not just flags. check_ping host=www.google.com total=true now works alongside the bare-flag form โ€” the form REST already used. (#1338)
  • Activate several modules in one command: nscp settings --active-module CheckSystem CheckNet. (#1329-follow-up)
  • Clear dual licence. NSClient++ is now Apache-2.0 OR GPL-2.0-only, with machine-readable REUSE metadata and third-party notices. (#1343)
  • Reworked, multi-OS documentation that presents Windows and Linux options and features together instead of assuming one platform. (#1342)

Detailed changes

CheckSecurity โ€” new host security-posture module

A new module, CheckSecurity (alias security), checks whether a host is in the security state you expect. Each check is a normal modern_filter check, so you can override the default warn/crit expressions, filter, and detail-syntax/top-syntax as usual.

Command Platforms What it checks
check_certificate All X.509 certificate expiry / validity / hygiene from files or the Windows store
check_users Windows + Linux Count and detail of logged-on / RDP sessions
check_firewall Windows only Firewall profile (Domain/Private/Public) enabled and active state
check_antivirus Windows only Registered antivirus products’ enabled / up-to-date state (Security Center)
check_bitlocker Windows only BitLocker drive-encryption protection status per volume
check_secureboot Windows only Whether UEFI Secure Boot is enabled (distinguishes “disabled” from “legacy”)
check_nla Windows only Network Location Awareness category (public/private/domain) per network

check_certificate defaults to warning when a certificate expires within 30 days and critical within 10 (matching common practice), emits expires_in (whole days until expiry) as perfdata, and can scan a whole directory:

check_certificate file=/etc/ssl/certs/mysite.pem
check_certificate file=/etc/ssl/certs recursive=true "detail-syntax=${subject}: ${expires_in}d"
check_certificate file=/etc/pki/tls/certs critical=expired=1

The Windows checks expose the raw state fields so you can tighten or relax the default posture. check_firewall adds an active flag (which profile is currently in effect) alongside enabled, so you can warn when a machine silently falls back to the Public profile after a network change:

check_firewall "warn=active = 1 and profile = 'Public'" "detail-syntax=${profile} profile is active"
check_secureboot "warn=supported = 0" "crit=supported = 1 and enabled = 0"
check_nla "crit=connected = 1 and category != 'domain'" "detail-syntax=${network}=${category}"

On a platform where a Windows-only check does not apply, the check returns UNKNOWN with a clear message rather than failing.

CheckNet โ€” check_http JSON path extraction

check_http can now pull values out of a JSON response body and treat them as filter keywords. Each json-path=alias:path option extracts the value at a dotted path (numeric segments index into arrays; single-quote a segment that itself contains a dot) and makes it available for warning=/critical= expressions and perfdata:

check_http url=https://api.example.com/health "json-path=qlen:data.queue.length" "crit=qlen > 100"
check_http url=https://api.example.com/health "json-path=st:status" "crit=st != 'ok'"
check_http url=https://api.example.com/health "json-path=err:metrics.error_rate" "warn=err > 0.01" "crit=err > 0.05"
check_http url=https://api.example.com/health "json-path=first:items.0.name" "json-path=cfg:'a.b'.c"

Numeric values keep full precision, strings compare and render as strings, and booleans read as 1/0. A missing path โ€” or a body that is not valid JSON โ€” leaves the alias empty rather than failing the check, and multiple json-path options can be combined freely.

CheckNet โ€” default performance data

CheckNet queries now attach sensible performance data by default, so check_http, check_tcp and the other network checks produce graphable perfdata without a hand-written perf syntax. check_ntp_offset threshold handling was also tidied up in the same change.

Check arguments โ€” boolean options accept values

Boolean check options now accept an explicit value in addition to the bare-flag form:

check_ping host=www.google.com total=true

Previously the value form was rejected from the CLI even though REST always passes flags as key=true tokens, so a boolean option that worked over REST could look broken from the command line. Both forms now behave identically.

CheckSystem โ€” process total aggregation

check_process process-total aggregation now correctly reports the started and hung states (on both Windows and Linux), so totals of these statuses match what the per-process detail shows.

Settings โ€” activate multiple modules at once

nscp settings --active-module now accepts several module names in one invocation:

nscp settings --active-module CheckSystem CheckNet

Licensing โ€” dual-licensed Apache-2.0 OR GPL-2.0-only

NSClient++ is now explicitly dual-licensed under Apache-2.0 OR GPL-2.0-only. Source headers were updated to SPDX identifiers, the project carries machine-readable REUSE metadata (REUSE.toml, LICENSES/), and a THIRD-PARTY-NOTICES.md / NOTICE collect the third-party licences. The installer, packaging and docs licence text were updated to match.

Build โ€” Python library discovery

CMake now derives the default Python library name instead of hardcoding a version, and defaults it to the soname so the module loads without the Python development packages installed. This makes Linux builds far less sensitive to the exact Python version on the build and target hosts. (#1334)

Documentation

  • Multi-OS reference docs. The reference documentation was reworked to present Windows and Linux options and features together, handling checks whose options diverge by platform instead of documenting a single OS. Windows docs were regenerated. (#1342)
  • New docs/samples/ usage examples and descriptions for every new CheckSecurity command and the check_http JSON feature.
  • check_process docs cross-reference filter_perf for top-N processes. (#1330)
  • README restructured and dead files removed. (#1340)

Quality and CI

  • Spelling. A codespell GitHub workflow was added and spelling errors in log messages and settings descriptions were fixed. (#1314, #1344)
  • Live integration tests. A new opt-in test suite runs checks against a real VM in Azure, alongside the existing REST-driven integration tests. New integration tests cover CheckSecurity, the check_http JSON feature, and --active-module. (#1335)
  • Assorted build fixes for older Windows toolchains, Linux, and sanitizer runs.

Upgrade notes

  • Licence change: NSClient++ is now distributed as Apache-2.0 OR GPL-2.0-only. This is a clarification/relicensing โ€” review it if your organisation tracks the exact licence of bundled software. No code or runtime behaviour changes as a result.
  • CheckNet perfdata is now on by default. Network checks emit performance data without an explicit perf syntax. If you were adding perfdata manually, double-check you are not now emitting it twice; graphs that previously showed nothing will start populating.
  • Boolean check arguments: option=true / option=false now work from the CLI as well as over REST. Existing bare-flag usage is unchanged.
  • CheckSecurity is not loaded by default. Enable it before using the new checks, e.g. nscp settings --active-module CheckSecurity. Windows-only checks return UNKNOWN on other platforms rather than erroring.

Download

You can download the new version from GitHub

// Michael Medin

0.14.0 Linux parity

This release brings Linux up to near-parity with Windows and completes the Linux story that began in 0.13.0. On the checks side it adds a full suite of Linux-native system checks (CheckSystemUnix) sourced directly from /proc and /sys, event-driven real-time monitoring on Linux, Linux disk / file / mount support in CheckDisk, and a round of cross-platform CheckNet improvements โ€” TLS for check_tcp, a fuller check_http, multi-record-type DNS, and two new network checks. Around the daemon it delivers , a secure-by-default web server, one-command installs via winget / Chocolatey / Scoop and nscp web install-ui, and a broad set of security and reliability fixes. It also hardens plugin shutdown so a misbehaving module can no longer crash the service on exit.

๐ŸŒŸ Highlights

  • Linux system checks (CheckSystemUnix). New native checks โ€” check_load, check_cpu_utilization, check_kernel_stats, check_swap_io, check_cpu_frequency, check_temperature, check_battery, check_network โ€” plus overhauled check_process (with process history / delta CPU) and a systemd-aware check_service. All read /proc and /sys directly, with thresholds and syntax that match their Windows counterparts.
  • Real-time monitoring on Linux. CheckSystemUnix gains an event-driven real-time thread, so CPU, memory and process alerts can fire the moment a threshold is crossed rather than only on poll โ€” the same real-time model previously available only on Windows.
  • Disk, file and mount checks on Linux (CheckDisk). CheckDisk is no longer Windows-only: free-space (check_drivesize), file (check_files) and disk-I/O checks now run on Linux, with per-device I/O sampling from /proc/diskstats, LVM / device-mapper mapping, inode statistics, file-integrity checksums, and a new check_mount.
  • TLS-aware network checks (CheckNet). check_tcp now speaks TLS (ssl=true) with new SPOP / SIMAP / SSMTP presets; check_http gains redirect policy, certificate-expiry reporting, Basic auth, SNI and non-GET methods; check_dns queries any record type against a custom resolver; and two new checks arrive โ€” check_ssh and check_nsclient_web_online.
  • First-class Linux packaging. The build follows the FHS / CMAKE_INSTALL_PREFIX, with official .deb/.rpm targeting /usr, and a Boost.Beast web backend by default.
  • One-command installs everywhere. Windows via winget / Chocolatey / Scoop; the Linux web UI via nscp web install-ui.
  • Secure by default. The web server refuses to serve cleartext HTTP without an explicit opt-in, plus check_nt command allow-listing and stricter external-script argument checks.
  • Run Lua scripts straight from the CLI with nscp lua execute, backed by Lua thread-safety hardening.
  • Safer plugin shutdown. The plugin manager isolates broken plugins and tears modules down cleanly, so a module that fails to unload can no longer take the service down on shutdown.

๐Ÿ“– Detailed changes

๐Ÿง CheckSystemUnix โ€” native Linux system checks

A new family of checks reads Linux kernel state directly. Thresholds and detail-syntax keywords mirror the Windows checks so alerts port across platforms.

Command Source What it reports
check_load /proc/loadavg 1/5/15-minute run-queue averages; load shortcut; percpu=true scaling
check_cpu_utilization /proc/stat (~1s sample) Per-mode breakdown โ€” user, system, iowait, steal, idle, total
check_kernel_stats /proc/stat, /proc/loadavg Context-switch rate, fork/process-creation rate, live thread count
check_swap_io /proc/vmstat Swap paging rates (swap_in/swap_out pages/s and bytes/s)
check_cpu_frequency /sys cpufreq Current / max / min CPU frequency
check_temperature thermal zones + hwmon Thermal-zone and hwmon sensor temperatures
check_battery /sys power_supply Charge level, power source, health
check_network /proc/net/dev + sysfs Per-interface link status and throughput
check_load "warn=load5 > 4" "crit=load5 > 8"
check_cpu_utilization "warn=iowait > 20" "crit=iowait > 50"
check_swap_io "warn=swap_out > 100" "crit=swap_out > 1000"
check_kernel_stats "warn=current > 8000" "crit=current > 10000"

โš™๏ธ CheckSystemUnix โ€” check_process history and check_service on systemd

  • check_process now tracks process history and computes delta CPU between samples (rather than lifetime CPU), and exposes memory keywords (rss, vms), matching the Windows process semantics.
  • check_service now inspects systemd units. The raw systemd state is mapped to a normalised state keyword so thresholds read the same as on Windows, while the raw fields (active, sub_state, preset) are exposed too. The default critical expression is ( state not in ('running', 'oneshot', 'static') or active = 'failed' ) and preset != 'disabled' โ€” so a stopped-but-disabled unit stays OK while an enabled unit that failed is CRITICAL. Per-unit process metrics (rss, vms, cpu, tasks, age) are parsed from /proc for the unit’s main process.
check_service service=cron "detail-syntax=${name}=${state} active=${active} preset=${preset}"
check_service service=mysql "warn=rss > 1G" "crit=rss > 2G"
  • check_os_version now parses /etc/os-release and reports the distribution and kernel details.

โšก CheckSystemUnix โ€” real-time monitoring

CheckSystemUnix gains a real-time collection thread and real-time data model, bringing event-driven checks to Linux. CPU, memory and process real-time filters evaluate continuously and emit the moment a threshold is crossed, matching the Windows real-time behaviour. See the Real-Time System Monitoring scenario, now cross-platform.

๐Ÿ’พ CheckDisk โ€” now on Linux: disk metrics, inodes, checksums, and check_mount

CheckDisk is no longer Windows-only. Linux builds gain the core free-space and file checks (check_drivesize, check_files) plus disk-I/O sampling, and this release adds:

  • Linux disk I/O. check_disk_io and check_disk_health now sample per-device I/O from /proc/diskstats once per second on Linux (mirroring the Windows PDH path). LVM / device-mapper and RAID volumes are mapped back to their backing devices via sysfs, so space and I/O join correctly for /dev/mapper/โ€ฆ filesystems. The first query after startup can return UNKNOWN while the collector takes its first sample.
  • Inode statistics. check_drivesize exposes inodes_total, inodes_free, inodes_used, inodes_free_pct and inodes_used_pct, so you can catch inode exhaustion (free bytes but no free inodes).
  • File-integrity checksums. check_files exposes md5_checksum, sha1_checksum, sha256_checksum, sha384_checksum and sha512_checksum, computed lazily only when referenced.
  • check_mount (new). Verifies a filesystem is mounted โ€” and optionally that it is mounted with the expected type and options โ€” reading the live mount table (/proc/self/mounts). A path that is not mounted is CRITICAL; a fstype or missing-options mismatch is WARNING.
check_drivesize drive=/ "warn=used>80%" "crit=used>90%"
check_drivesize drive=/ "warn=inodes_used_pct > 85" "crit=inodes_used_pct > 95"
check_files path=/var/log pattern=*.log "crit=size>100M"
check_mount mount=/data fstype=ext4

(Some Windows-only legacy CheckDisk commands are not registered on Linux.)

๐Ÿ” CheckNet โ€” TLS for check_tcp

check_tcp can now establish a TLS session over the connected socket (ssl=true), with tls-version (default tlsv1.2+), verify (default none) and ca options, and a response regex to match the server’s greeting. Three new TLS service presets ship alongside the existing plaintext ones:

Preset Port TLS Expected greeting
SPOP 995 yes ^\+OK
SIMAP 993 yes ^\* OK
SSMTP 465 yes ^220
check_tcp host=pop.example.com ssl=true "response=^\+OK"
check_tcp host=imap.example.com SIMAP

Peers that close the TLS session without a close_notify (reported by OpenSSL as stream_truncated) are now treated as a clean end-of-data rather than a read failure.

๐ŸŒ CheckNet โ€” check_http features

check_http gains the features needed for real service checks:

  • Redirect policy โ€” onredirect=ok|follow (default ok) with max-redirs (default 15); follows 301/302/303/307/308.
  • Certificate expiry โ€” reports ssl_expiry_days (days until the served certificate expires) for HTTPS targets.
  • Authentication โ€” username / password send an HTTP Basic Authorization header.
  • Methods and bodies โ€” method= (HEAD/POST/โ€ฆ), post-data, content-type; supplying post-data with a GET promotes the request to POST.
  • SNI โ€” sni= overrides the TLS server name / verification host.
check_http url=https://example.com method=HEAD
check_http url=https://example.com username=user password=secret
check_http url=http://example.com/old onredirect=follow
check_http url=https://example.com "warn=ssl_expiry_days < 30" "crit=ssl_expiry_days < 7"

๐Ÿ”Ž CheckNet โ€” check_dns record types and custom server

check_dns now queries any record type (type=A|AAAA|MX|TXT|NS|CNAME|SOA|PTR|SRV) and can direct the query at a specific resolver (server=), rather than only resolving an A record against the system resolver.

check_dns host=example.com type=MX server=8.8.8.8

๐Ÿ”‘ CheckNet โ€” check_ssh (new)

Connects to an SSH port and validates the protocol banner (implemented on top of the check_tcp service-preset machinery). Flags a server that fails to present a valid SSH-2.0 / SSH-1.x identification string.

check_ssh host=server.example.com
check_ssh host=server.example.com port=2222

๐Ÿ“ก CheckNet โ€” check_nsclient_web_online (new)

Verifies that a remote NSClient++ agent’s REST/WEB endpoint is reachable and that credentials authenticate โ€” a lightweight liveness probe for the agent’s management interface. Reports the base URL and distinguishes “unreachable” from “authentication failed (HTTP 401/403)”.

check_nsclient_web_online url=https://agent:8443 password=... verify=none

This command is deliberately named _online because it only checks reachability. A future check_nsclient_web will run actual remote checks through the endpoint.

๐ŸŒ™ Lua โ€” run scripts straight from the command line

nscp lua execute runs a Lua script directly from the CLI โ€” useful for developing and debugging check scripts without wiring them into the configuration first:

nscp lua execute --script myscript.lua

Lua also got thread-safety hardening (a proper GIL), new helpers for targeted and forwarded queries, clearer errors when a script fails to load, and log lines that report the actual script line number.

๐Ÿ” TLS โ€” outbound SNI and Op5 client options

  • SNI is now sent on outbound TLS connections (Graphite and the generic TLS client), so a TLS proxy hosting several certificates returns the right one.
  • The Op5 client gained explicit TLS settings:
[/settings/op5/client/targets/default]
tls version = 1.2+
verify mode = peer
ca = ${ca-path}

๐Ÿ”’ Security โ€” secure-by-default web server and hardening

  • The web server refuses to run unencrypted by default. To stop NSClient++ from silently serving the REST API / web UI over plain HTTP, the WEB server now refuses to start without a certificate unless you explicitly opt in with allow insecure = true (see Upgrade notes).
  • check_nt can now be restricted to specific commands. The legacy check_nt protocol is password-only (and source-IP filtering is spoofable), so you can now limit which of its ten request codes are answered. The default is any (unchanged behaviour):
[/settings/NSClient/server]
# Answer only harmless system metrics; deny arbitrary counter/file reads
# and service/process enumeration:
allow = metrics, info

A request outside the list is rejected with ERROR: Command not allowed. - Stricter shell-metacharacter checks in external scripts. User-supplied argument values containing more shell metacharacters are now rejected. - Graphite metric paths are sanitized before being written to the line protocol, preventing injection of extra metrics. - Python sys.path handling hardened to prevent code-injection via path manipulation.

๐ŸชŸ Windows โ€” winget / Chocolatey / Scoop packages

NSClient++ is now published to the common Windows package managers:

winget install Mickem.NSClient
choco install nsclient   # Still pending approval
scoop install nsclient   # still pending approval

๐Ÿ“ฆ Linux packaging โ€” FHS layout and install prefix

The Linux build honours CMAKE_INSTALL_PREFIX like a normal CMake project, and the official .deb/.rpm are built for /usr. The file layout is now:

What Location
Daemon /usr/sbin/nscp
Modules /usr/lib/nsclient/modules
Private libs /usr/lib/nsclient
Config /etc/nsclient
State / logs /var/lib/nsclient ยท /var/log/nsclient

If you previously patched hardcoded paths to build for a custom location, that is no longer needed โ€” pass -DCMAKE_INSTALL_PREFIX=/opt/nsclient (or the standard CMAKE_INSTALL_*DIR knobs) instead. To point an already-installed daemon at a boot.ini in a non-standard place there is a new override:

nscp service --run --path-override boot-conf=/etc/nsclient/boot.ini

๐Ÿ–ฅ๏ธ Linux โ€” web UI is a separate download (.deb / .rpm)

The Linux packages no longer bundle the React/Vite web frontend (Debian/Fedora policy forbids npm install during package builds). The daemon, REST API, NRPE/NSCA listeners and every check module are still in the package โ€” only the browser UI ships separately. After installing the package, fetch the matching UI bundle as root:

sudo nscp web install-ui      # downloads + verifies NSCP-Web-<version>.zip
sudo nscp web ui-status       # show installed version / source
sudo nscp web uninstall-ui    # remove only what install-ui put down

Until you do, the web port shows a small built-in placeholder page; the REST API and all listeners work normally without it. The Windows MSI still bundles the UI inline.

๐Ÿงฉ Core โ€” filter summary-variable rendering

All check filters now prefer summary variables during summary rendering. Previously a keyword that exists both per-item and as a summary aggregate (notably status) could render the last item’s value in the summary line, making the overall status read incorrectly. Summary context now resolves to the summary value, so top-syntax reports the aggregate correctly.

๐Ÿ›ก๏ธ Service โ€” safer plugin shutdown

The plugin manager now handles broken plugins defensively and prevents a module that misbehaves during teardown from crashing the service on shutdown. Modules get a clean teardown path so listeners and background threads stop before unload.

๐Ÿ“ˆ collectd client โ€” encoding and protocol fixes

Correct (little-endian) gauge encoding, working IPv6 multicast, a configurable send interval (default 10s), and previously dropped metric types (counter / derive / absolute) are now mapped instead of discarded.

๐Ÿ› Bug fixes

  • Fixed a Windows build break introduced during the Linux work.
  • Fixed regressions where some metrics and real-time checks stopped reporting.
  • Corrected check_ntp_offset threshold handling and improved default accuracy.
  • Improved check_connections performance-data accuracy for total connections.
  • Yet another possible fix for installer deleting config on upgrade.
  • Fix unreliable per-process CPU% from check_process delta=true. The delta calculation for per-process CPU usage produced inconsistent readings; it now returns stable, accurate values.
  • Fix perf-config=none reporting “Failed to parse syntax”. Setting perf-config=none to suppress performance-data formatting no longer fails parsing.
  • Fixed http(s) headers should be case-insensitive.
  • IPv6: listeners set IPV6_V6ONLY on Linux to avoid port conflicts with IPv4, and IPv6 address resolution was improved.
  • Thread-safety: logger subscriber management, the scheduler, and timer callbacks were made properly thread-safe; CommandClient now shuts down gracefully on POSIX signals.
  • check_mk server: fixed a memory leak.

๐Ÿšš Packaging & distribution notes

  • The bundled check_nsclient Nagios plugin moved to its own repository (mickem/check_nsclient) and is pulled in at build time. This only matters if you build from source.
  • Package/file names were normalised โ€” double-check the exact asset name on the releases page if you script downloads.
  • Reduced Linux build dependencies: the build now uses libzip (instead of vendored Miniz), can use the system Google Test, and degrades cleanly when an optional dependency is missing. Linux uses the Boost.Beast web backend by default.

๐Ÿ“š Documentation and tests

  • New Linux Server Health scenario, plus updated cross-platform Network Checks, Disk Space Alerting, Service & Process Monitoring and Real-Time System Monitoring scenarios.
  • New docs/samples/ usage examples and clarifying descriptions for every new command.
  • Extensive new unit tests (CheckSystemUnix, CheckDisk unix, CheckNet) and REST-driven integration tests under tests/ covering the new system, disk and network checks.

โš ๏ธ Upgrade notes

A few defaults were tightened for security and the Linux packaging layout changed. None of these affect a normal Windows MSI upgrade, but Linux users and anyone running the web server in cleartext should read this section.

  • The web server refuses to run unencrypted by default. If you intentionally run the web server in cleartext (e.g. behind a TLS-terminating proxy, or on an isolated network), set allow insecure = true:
[/settings/WEB/server]
allow insecure = true

Otherwise, provide a certificate (certificate = โ€ฆ). If you do nothing and the server has no certificate, it logs an error and does not start the listener. - The web UI is a separate download on Linux (.deb / .rpm). After installing the package, run sudo nscp web install-ui to fetch the matching UI bundle. Until then the web port serves a built-in placeholder; the REST API and all listeners work normally without it. The Windows MSI still bundles the UI inline. - Linux install layout now follows the FHS / install prefix. The official .deb/.rpm install to /usr (daemon /usr/sbin/nscp, config /etc/nsclient, state/logs under /var). If you patched hardcoded paths to build for a custom location, pass -DCMAKE_INSTALL_PREFIX (and the standard CMAKE_INSTALL_*DIR knobs) instead. - Linux check_service now targets systemd. If you previously scripted around the old behaviour, note the normalised state keyword and the default expression that keeps disabled units OK. Match units by unit name (e.g. service=ssh), and use state, active, sub_state and preset in thresholds. - Linux check_process reports delta CPU. CPU is now the usage between samples rather than lifetime CPU. Review any CPU thresholds that assumed the old semantics. - Linux disk I/O needs one collector sample. The first check_disk_io / check_disk_health query immediately after startup may return UNKNOWN (“collector still initializing”); this is invisible with a running service and normal in one-shot testing. - check_tcp / check_http boolean ssl. Enable TLS with ssl=true. When verifying certificates, set verify=peer and provide a ca= bundle; the default remains verify=none.

Download

You can download the new version from GitHub

// Michael Medin

0.12.6 New permission system

The release has three big stories โ€” a new core permission system with optional client-cert principals on NRPE, a PDH overhaul that fixes long-standing counter-collection crashes and adds counter functions, and a WEB hardening option that lets monitoring-only deployments expose the WEB UI without seeding a privileged admin account. Everything else is bug fixes, small features, and follow-ups around those three threads.


Highlights

  • Core permission system โ€” opt-in policy layer that gates which caller can run which command. Configured under /settings/permissions. Disabled by default; existing installs keep working. See https://nsclient.org/docs/concepts/permissions/ for the model, identity table, and rollout recipe.
  • NRPE client identity from cert CN โ€” when client identity source = cn is set on NRPEServer and the listener verifies the client cert, the CN is stamped as the policy principal so rules can be written per-cert ( NRPEServer:icinga-master = ...). Hard guardrail at module start refuses to load the module if the TLS verify mode would let the CN be attacker-supplied.
  • Global allow exec toggle โ€” exec is now gated by a single on/off switch under /settings/permissions. The per-command rule table applies to queries only. Default true so enabling the policy system does not break exec callers.
  • PDH (performance counter) overhaul โ€” fixes for service crashes when PDH misbehaves (#592, #547), counter retry when temporarily unavailable (#634), reliable English counter lookup (#652, #906), a resource leak in the counter-lookup path, and a refactor to smart-buffer-based PDH enumeration. Most users running CheckSystem on Windows should see meaningfully better reliability.
  • check_pdh counter scaling and functions (#281) โ€” details-syntax and related rendering paths can now apply scaling and other functions, e.g. '${counter}'=${value:scale(/1024)}MB.
  • check_network โ€” human-readable strings, scaling, speed, and percentages (#329); team-network statistics (#625). See https://nsclient.org/docs/reference/check/CheckNet.
  • Nagios range syntax in performance data (#748) โ€” 1:10, ~:5, @10:20 etc. work in perfdata thresholds, matching the Nagios plugin spec.
  • disable admin user on WEBServer โ€” monitoring-only deployments can expose the WEB UI without ever seeding the built-in admin (and previously seeded admin entries are ignored). Pairs naturally with the new permission system to lock down reconfiguration surfaces.
  • Path overrides moved to boot.ini + new --path-override CLI flag โ€” path tokens (module-path, certificate-path, etc.) are now declared early in boot.ini so they take effect before the main config is loaded. Per-invocation overrides via --path-override KEY=VALUE. See https://nsclient.org/docs/concepts/settings.
  • NRPE startup is no longer fatal on listener failure โ€” bad bind address / port already in use logs a clear error and leaves the module loaded so settings and commands stay usable for diagnostics.
  • Dual-stack listening fixed (#312) โ€” v4 and v6 acceptors no longer trample each other’s pending connection slot.
  • disable admin user, client identity source, allow exec, and the policy table are all documented in https://nsclient.org/docs/concepts/permissions/ and https://nsclient.org/docs/setup/securing. Treat those two as the starting point for any new install.

Detailed changes

Security and permissions

Core permission system A policy layer in the core decides whether a given caller may run a given command. Disabled by default; when enabled, rules form a strict allow-list.

[/settings/permissions]
enabled = true
log denials = true
log allows = false      ; noisy, only flip on while rolling out
allow exec = true       ; queries-only rule table; exec is a global toggle

[/settings/permissions/policies]
NRPEServer = CheckHelpers.*, CheckSystem.check_cpu
WEBServer:admin   = *
WEBServer:viewer  = CheckSystem.check_cpu, CheckSystem.check_drivesize
Scheduler = CheckHelpers.*, CheckSystem.*

Subject is module[:principal]; object is module.command. Wildcards (*, ?) supported. Rules combine additively. See https://nsclient.org/docs/concepts/permissions/ for the full identity model, the CheckHelpers identity-forwarding behaviour, and a step-by-step rollout recipe.

NRPE client cert CN as principal When two-way TLS is configured and verifying client certs against your CA, the Common Name is stamped as the policy principal:

[/settings/NRPE/server]
client identity source = cn        ; default: none
verify mode = peer-cert
ca = /etc/nsclient/ca.pem
[/settings/permissions/policies]
NRPEServer:icinga-master   = CheckHelpers.*, CheckSystem.*
NRPEServer:metrics-shipper = CheckSystem.check_cpu, CheckSystem.check_drivesize

Guardrails: the module refuses to start if client identity source = cn is configured without SSL, without verify_mode containing peer and fail-if-no-peer-cert (or the peer-cert alias), or without a non-empty ca path. The CN is logged at debug level on every accepted handshake for diagnostics. CN-only (not full DN) because INI key syntax uses = as the key/value separator and would corrupt DN-shaped policy keys; see the “Why CN-only” section of the permissions doc. See https://nsclient.org/docs/reference/client/NRPEServer.

Global allow exec toggle Per-command rules apply to queries only. The exec surface (WEB scripts UI, lua/python core:simple_exec(...), CLI exec) is gated by a single boolean:

[/settings/permissions]
allow exec = false   ; hard lockdown; default is true

When false and enabled = true, every exec call returns Permission denied: exec is globally disabled (/settings/permissions/allow exec = false). See “Why exec is a single toggle” in https://nsclient.org/docs/concepts/permissions/.

disable admin user on WEBServer For installations that expose the WEB UI for status/visualisation only and never want a remote-reconfiguration surface:

[/settings/WEB/server]
disable admin user = true

With this set, the built-in admin is not seeded on first boot, and any existing admin entry in the user settings is ignored at load time.

Security guide updates https://nsclient.org/docs/setup/securing was rewritten with concrete configurations for NRPE (with and without mTLS) and the WEB server. Read it before exposing either to a network you don’t fully control.


Performance counters / PDH

The PDH subsystem (the Windows performance-counter collection backbone behind CheckSystem, check_cpu, check_pdh, check_network, etc.) got a substantial reliability pass. Most users running NSClient++ as a long-running service on Windows should see fewer crashes and more consistent results.

  • Service crashes when PDH misbehaves on a particular machine (#592, #547) โ€” root-caused and fixed. Misbehaving counter registrations no longer take the service down.
  • Counter not retried if unavailable (#634) โ€” counters that fail to bind at first sight now get retried on subsequent collection cycles, instead of being permanently unhealthy for the lifetime of the process.
  • English counter lookup improved (#652, #906) โ€” addresses reading of localised counters by their canonical English names on non- English Windows installs.
  • Resource leak in PDH counter lookup fixed.
  • PDH enumeration refactored to smart buffers โ€” clearer memory ownership across the enumeration path, fewer footguns for future changes.
  • check_pdh counter scaling and functions (#281) โ€” all the details-syntax / rendering paths can now apply functions. Examples:
    check_pdh "counter=\Processor(_Total)\% Processor Time" \
              "details-syntax=${counter} = ${value:round(2)}%"
    
    See https://nsclient.org/docs/reference/check/CheckSystem for the function reference.

check_network

  • Human-readable strings, scaling, speed, and percentages (#329) โ€” perfdata and message output now render numbers in a way operators actually want to read:
    check_network 'filter=interface=Ethernet' \
                  'top-syntax=${list}' \
                  'detail-syntax=${interface}: ${total_rx_human}/s in, ${total_tx_human}/s out'
    
  • Team network statistics (#625) โ€” aggregate stats across Windows NIC teams.

See https://nsclient.org/docs/check/CheckNet.


Performance data formatting

  • Nagios range syntax in performance data (#748) โ€” the perfdata threshold fields now accept the standard Nagios range syntax: 5:10, ~:5, @10:20, etc. Brings NSClient++ into line with what Nagios consumers already expect.

Settings, paths, and CLI

  • Path overrides moved to boot.ini โ€” path tokens (module-path, certificate-path, data-path, log-path, โ€ฆ) now live under [paths] in boot.ini (next to nscp.exe), not in nsclient.ini. Overrides take effect before the main config is loaded โ€” including the bootstrap step that decides where the main config itself lives.
    ; boot.ini
    [paths]
    module-path = D:\monitoring\modules
    certificate-path = D:\monitoring\certs
    
  • --path-override CLI flag โ€” per-invocation override, repeatable. (Renamed from --path to avoid colliding with the nscp settings --path subcommand option.)
    nscp client --path-override module-path=/build/modules --path-override log-path=. ...
    
  • See https://nsclient.org/docs/concepts/settings for the precedence rules and the migration note for installs that had a [/paths] section in nsclient.ini.

Aliases and command registration

  • CheckHelpers alias โ€” aliases can now be defined under [/settings/check helpers/alias] and are registered by CheckHelpers directly, without requiring CheckExternalScripts to be loaded. This is the preferred place going forward; the legacy [/settings/external scripts/alias] is still honoured for backward compatibility.
  • API to list registered query aliases (#506) โ€” programmatic introspection of the alias table, useful for tooling.
  • simple_command / simple_command_map โ€” internal refactor that streamlines how modules register aliases. No user-visible behaviour change, but module authors may want to look at the new pattern.
  • Icinga client alias (7c49a3d3) โ€” minor module-specific addition.

NRPEServer

  • Listener failure no longer kills the module โ€” a bad bind to address that the resolver can’t look up, or a port already in use, used to make the whole module fail to load. Now the failure is logged clearly, the listener stays down, and the module’s settings and commands remain accessible for diagnostics and reconfiguration. Fix the config and reload โ€” no service restart needed.
  • Dual-stack fixed (#312) โ€” the v4 and v6 acceptors used to share a single pending-connection slot, which caused intermittent Already open errors on v6 once v4 accepted a client. Each family now owns its own slot.
  • Insecure mode produces an error-level log line โ€” flipping insecure = true (for legacy check_nrpe interop) now surfaces as an ERROR so it shows up in monitoring dashboards, instead of silently disabling cert-based peer auth.

Plugin lifecycle

  • prepare_shutdown hook โ€” modules can opt in to a first-phase shutdown pass before any plugin is unloaded. Used by the Scheduler and similar long-running submitters to finish in-flight work cleanly. Operators see fewer “submission failed during shutdown” lines during service stop.

Settings store

  • simpleini buffer NUL-termination fix โ€” fixes a buffer allocation issue in the INI parser that could affect non-UTF-8 data paths.
  • cache allowed host is now a real boolean โ€” previously parsed as a string with surprising truthiness; matches what the docs always claimed.

Modules and clean-ups

  • WMI module refactor โ€” target handling and settings management cleaned up.
  • IcingaClient cleanup โ€” removed unused command-handling code paths.
  • CheckLogFile config and descriptions โ€” fixed misleading defaults and improved the help text.
  • Web UI improvements โ€” more settings elements exposed under modules, simpler module configuration. Web dependencies refreshed.
  • Installer: UninstallString is now correct (#495) โ€” removal via Windows “Apps & Features” works again.
  • Rust dependencies bumped.

Upgrade notes

Most installs can upgrade in place โ€” defaults are preserved. Read the specific items below if any of them apply.

Permission system

The new policy layer is disabled by default. Existing installs continue to behave exactly as before until an operator opts in via /settings/permissions/enabled = true.

If you do opt in:

  • Per-command rules under /settings/permissions/policies apply to queries only. Any rules you might have written for exec command patterns will be silently ignored for the exec dispatch path โ€” exec is gated by the single global allow exec boolean.
  • The default for allow exec is true, so enabling the policy will not silently break the WEB scripts UI, lua/python core:simple_exec(...), or CLI exec. Flip to false only if you want a hard exec lockdown.
  • Roll out with log allows = true first so you can inventory what your actual traffic looks like before tightening to a real allow-list. See the step-by-step recipe in https://nsclient.org/docs/concepts/permissions/.

NRPEServer

  • The new client identity source setting defaults to none, which matches the previous behaviour (subject is bare NRPEServer). Set to cn only when you want per-cert principals โ€” and only after you’ve configured verify_mode = peer-cert and a ca path. The module will refuse to start with a clear error if you set cn without those.
  • Pin the ca path to your private monitoring CA. The system trust store (Windows root store / Linux distro bundle) accepts certs from every public CA on the planet and would let an attacker with a public cert choose their own CN. See “Pin to a private CA” in the permissions doc.

Path overrides

  • If you had a [/paths] section in nsclient.ini from an older NSClient++ install, those overrides moved to [paths] in boot.ini (note: same section name, different file). There is no automatic migration. Copy each key = value to a [paths] section in boot.ini (next to nscp.exe) and delete the old section from nsclient.ini.

WEB server

  • The new disable admin user = true setting is opt-in. Existing installs keep their admin and continue to work unchanged. Use this when you want to expose the WEB UI for status-only viewing and have no need to reconfigure the agent through the web.

NRPEServer startup robustness

  • A failed listener (bad bind address, port in use) used to make the whole NRPEServer module fail to load. It now logs an ERROR and leaves the module loaded with no active listener โ€” so you can reconfigure via nscp settings --path /settings/NRPE/server --key ... --set ... and reload, without restarting the service. If you had monitoring on “module load failed” specifically, you may want to add “NRPE listener failed” as a separate signal.

insecure = true on NRPEServer

  • This option (for legacy check_nrpe interop) now logs at ERROR rather than DEBUG/INFO. Behaviour is unchanged; the message is louder so it shows up in dashboards. If your monitoring filters by severity, you may want to whitelist this specific message on agents that intentionally run in insecure mode.

cache allowed host

  • Previously parsed as a string with surprising truthiness; now a real boolean. If you had cache allowed host = yes or = on, switch to true. Numeric 1 / 0 still work.

Nagios range syntax in performance data

  • This is additive โ€” existing perfdata that doesn’t use range syntax continues to work. Plain numbers still parse as before. Only consumers that previously had to special-case NSClient++’s output may need adjusting, but most Nagios-ecosystem tools handle both forms.

Download

You can download the new version from GitHub

// Michael Medin