Upgrading¶
What to do when upgrading NSClient++, newest release first. Most upgrades are in place โ defaults are preserved and the default install is usually unaffected โ but the items below change observable behaviour or want a configuration touch. Read the entries between the version you are on and the version you are moving to.
Each entry carries an icon for the area it touches. ๐ is the one that matters: those entries are security-relevant, and the Security notices page tracks them in one place. An entry badged Action required needs a change on every installation that runs the module; one badged Check your setup only if you use the feature it describes; the rest are informational. Full per-release detail lives in each GitHub release.
Use the filter below to narrow the list to your setup: pick the version you
are coming from and tick the modules your nsclient.ini enables, and only the
entries that concern that installation stay visible. The selection is shared
with the Security notices page, remembered in your browser and reflected in
the page address, so a filtered view can be bookmarked or shared.
Modules: all
Tick the modules your nsclient.ini enables. Entries that touch none of the ticked modules are hidden; service covers the agent itself, which every installation runs. The selection is shared between the Upgrading and Security notices pages.
0.23.0¶
- ๐ ๐ฅ Action required A listener
verify modeit does not recognise now refuses to start. Check every listener you have configured for mutual TLS before upgrading. The parser used to drop a token it did not know, andfail-if-no-peer-certโ the spelling the documentation tells you to write โ was one of those tokens. Soverify mode = peer,fail-if-no-peer-certresolved to barepeer: the listener asked for a client certificate and completed the handshake when none arrived, leaving an “NRPE with mutual TLS” that was really NRPE with an IP filter. Both spellings are now accepted, along withcertificateforpeerandclient-certificate, and whitespace around a token is ignored. Anything else makes the listener log the offending token and refuse to start, which is the loud version of the same mistake. See the security notice.
-
๐ ๐ฅ Check your setup The NSCP and check_mk clients, and
check_nsclient_web_online, now verify the server certificate by default. All three encrypted their connection but never authenticated the peer:verify modedefaulted tononeon anNSCPClienttarget, was left unset on aCheckMKClienttarget (an empty verify mode parses the same asnone), andcheck_nsclient_web_onlinedefaultedverifytonone.verify mode/verifynow defaults topeerandcato the agent’s own trust bundle (${ca-path}โ the auto-generated ROOT store export on Windows, the distribution bundle elsewhere).Because an NSClient++ agent generates a self-signed certificate on first start, a relay or REST check pointed at a default agent will now fail the handshake where it used to connect. Pick one per target:
Situation Configuration The remote agent uses a certificate from your own CA ca = <the CA>(the defaultverify mode = peerthen works)The remote agent still uses its self-signed certificate ca = <that certificate>andverify mode = peer-certYou accept an unauthenticated link verify mode = none(encrypted, but an on-path attacker can impersonate the remote)[/settings/NSCP/client/targets/web01] address = nscp://192.168.56.103:8443 password = <the remote's admin password> ca = /etc/nsclient/agent-web01.pem verify mode = peer-certFor
check_nsclient_web_onlinethe same choice is made per check withca=andverify=:check_nsclient_web_online host=agent.example.com password=... \ ca=/etc/nsclient/agent.pem verify=peer-certCheckMKClienttargets are only affected when they enable TLS (use ssl = true); a stock check_mk agent listens in plain text and is unchanged.See the security notice.
- ๐ ๐ฅ Check your setup The op5 installer profile defaults to secure NRPE. Picking op5 on the
MSI’s monitoring-tool page used to force NRPE into legacy mode โ anonymous
Diffie-Hellman, no peer verification โ hide the mode radio group so the choice
could not be seen or changed, and enable
allow nasty charactersfor every NRPE-reachable check. The profile now defaults to the same secure mode the generic profile uses (verify mode = peer-cert,insecure = false), shows the NRPE mode radio group so legacy is a visible decision, and no longer setsallow nasty characters.allow argumentsstays on, because op5’s check commands need it; what changes is that a caller has to present a certificate first. An op5 host monitored by acheck_nrpewith no client certificate needs either a certificate or Insecure mode picked explicitly on that page. See the security notice.
- ๐ Check your setup A fleet management url that is not
https://is now refused. Nothing to do for a host enrolled against an https fleet server. Themtls_urlin the enrollment manifest is where desired state and signed bundles come from, and on a plain socket neither the agent’s client certificate nor the pinned server certificate does anything โ the whole management channel ran unauthenticated, silently. Enrollment now refuses a non-https management url, the sync loop refuses to start on a stored one, and the http client refuses to attach a client certificate or a pinned CA to a non-TLS transport at all. A url with no scheme counts as plaintext, because it is opened on a plain socket just the same. Where plaintext is what you want,nscp enroll --insecurerecords the decision in the manifest and[tls] allow plaintext = trueinboot.iniallows it for an older manifest; the sync then logsINSECUREon every start. See the security notice.
-
๐ Check your setup You stay logged in to the web UI across a restart of the agent. Restarting the service, or upgrading it, no longer logs every web user out: a browser tab, and a script holding a key from
/api/v2/login, keeps working. Sessions still expire eight hours after login, and only a hash of each session key is ever kept, in memory and on disk; see the security notice.To end a session, use the log out button in the web UI, or call
DELETE /api/v2/loginwith the key. It stops working immediately and does not come back, not even if the agent is killed rather than stopped cleanly.Changing a user’s password or role also ends that user’s sessions, and removing the user ends theirs โ but only from the next start of the agent. The running service keeps the users it read when it started, so until you restart it the new password does not work and the old sessions keep working. Reapplying settings from the UI is not enough; restart the service, or log the sessions out.
If you relied on a restart to log everyone out, set
persist sessions = falseunder[/settings/WEB/server]: sessions are then kept in memory only, as before, and every restart ends every session. Use the same switch to invalidate every session at once after a suspected leak (restart once with it off; switch it back on afterwards if you want sessions to survive the next restart).[/settings/WEB/server] ; Keep sessions in memory only, as before: every restart ends every session. persist sessions = falseOne limitation: a user whose password is written in cleartext in
[/settings/WEB/server/users/<user>]still has to log in again after a restart. Passwords stored in their hashed form (pbkdf2-sha256$โฆ, which is what the agent writes foradmin) are not affected. To convert a cleartext password, runnscp web add-user --user <user> --role <role>without--password: it keeps the password the user already has and stores it hashed.
- ๐ The Windows release build now verifies what it downloads. Nothing to do:
this affects the release build, not a running agent. The checksum gate added
last release was in place but every line in
.github/dependency-checksums.txtreadunrecorded, so each download warned and continued. The digests are recorded now for OpenSSL (cross-checked against OpenSSL’s own published.sha256), protobuf, Crypto++, miniz and the prebuiltcheck_nsclient.exe. TinyXML2, Mongoose and MariaDB Connector/C are no longer fetched as GitHub-generated tag archives but cloned and verified against a recorded commit, the way googletest already was. Lua stays unrecorded until its digest can be taken against the checksums lua.org publishes. Bumping a dependency version now means adding its line to that file first, because a missing line fails the build. See the security notice.
-
๐ง Check your setup An unrecognised
${...}path token is now an error, and a[paths]override has to name an absolute location. Nothing to do if your paths are spelled correctly and your overrides are absolute โ which is the normal case. If not, the agent now says so in the log instead of quietly using the wrong folder.Two related changes:
- An unknown token is reported and the setting that carried it is skipped.
It used to resolve to the installation directory, so a mistyped
${scripst}/check.batwas not rejected but turned into a real path under the install folder โ and whatever depended on it went somewhere nobody was looking. This is the same silent failure that made a pre-0.17${host}in a path produce a mangled name rather than an error. The tokens are still open ended: anything you define inboot.ini’s[paths]section counts as known. Note that${appdata}and${common-appdata}are Windows-only and are now rejected on unix, where they previously resolved to the install directory. - A
[paths]or--path-overrideentry that does not resolve to a location of its own is ignored, with an error naming it, and the built-in default applies. An override may still be written in terms of other tokens (scripts = ${shared-path}/mine); it is the resolved value that is judged. “A location of its own” is slightly wider than “absolute”: on Windows a drive-relativeC:mineand a root-relative\mineare accepted, because the operator plainly named a drive or a root and it is not ours to move them โ but note they still resolve against the process’s current drive or directory, so prefer a fully absolute path. A relative override would be read and written relative to the service’s working directory โC:\Windows\System32for a Windows service,/under a bare init script, the package directory under the shipped systemd unit โ so what it meant depended on how the agent happened to be started.
If you scripted
--path-override log-path=.or similar against a build tree, give it an absolute path instead.Check the log after upgrading: every rejected token and override is reported with the key that carried it.
- An unknown token is reported and the setting that carried it is skipped.
It used to resolve to the installation directory, so a mistyped
-
๐ง Check your setup A relative path in a setting that writes files is now taken relative to that setting’s own folder, not to the service’s working directory. Nothing to do if your paths are absolute or written with
${...}tokens. If any of the settings below is a bare relative name, the file it produces moves โ on unix usually not at all, on Windows usually into the folder it should always have been in.Expanding a path substitutes
${tokens}; it never made anything absolute. A value with neither a token nor a leading/was therefore resolved against whatever the service’s working directory happened to be, which isC:\Windows\System32for a Windows service,/under a bare init script, the package directory under the shipped systemd unit, and the shell’s directory fornscp test. Four different answers from the same configuration file.Settings whose consumers own a folder now say so, and a relative value lands there instead:
Setting Relative values now land in [/attachments]target${shared-path}[/settings/log] file name${log-path}[/settings/crash] archive folder${crash-folder}[/settings/fleet] managed path${fleet-folder}[/settings/filewriter] file${log-path}An absolute path, a UNC path or anything written with a token is used exactly as given โ pointing one of these somewhere specific is still yours to decide.
nonestill means “no file” wherever it was already accepted.One Windows value that did not used to be taken at its word now is. If
[/settings/log] file nameis literally/nsclient.logโ a value carried forward from a version where a leading slash meant the installation directory โ it was quietly rewritten to${exe-path}/nsclient.logand the log appeared beside the executable. It now names a root like any other absolute path, so the log lands atC:\nsclient.log, on the root of the system drive, where the service may well not be permitted to write. The compiled default was never this value, so only a configuration that sets it explicitly is affected.Set it to what you actually want โ
${log-path}/nsclient.logfor the normal location, or a barensclient.log, which now means the same thing:[/settings/log] file name = ${log-path}/nsclient.logOn Linux the attachment case generally does not move: the shipped systemd unit sets
WorkingDirectoryto the package directory, which is what${shared-path}resolves to, so relative attachments were already landing in the right place โ by coincidence rather than by design. Windows services and any non-systemd launch are where this changes something.SimpleFileWriter’s defaultfile = output.txtis affected by the same rule and now writes to${log-path}/output.txtrather than to the working directory.
-
๐ง Check your setup
file name = nonenow really switches the log off on Windows. Nothing to do unless[/settings/log] file nameis set tonone. Unix installations already behaved correctly.noneis documented as “no log file”, but the name was joined to the installation directory before the sentinel was tested. On Windows, where that directory is not empty,nonebecame a real file calledC:\Program Files\NSClient++noneand file logging stayed on. On unix the join contributed nothing, so the sentinel survived and the setting worked.If this applied to you, the stray file is left where it is โ delete it once you have checked you do not need its contents.
noneis now recognised by the path expander itself, so it is equally safe in every setting that accepts it, including everycaoption where it means “use the TLS library’s own trust store”.Where a bare log file name ends up is a separate change with its own note โ see the one about relative paths being taken relative to the folder their setting owns, which covers the log file on both platforms.
-
๐ง Check your setup Windows: a relative external-script command is resolved against the installation directory.
[/settings/external scripts/scripts]entries that carry a folder โ the conventionalcheck_foo = scripts\check_foo.bat, and whatnscp ext-scr add --importwrites โ are now rooted at${base-path}before they are launched. On Windows that is the same folder${scripts}points at, so the script is found wherever the agent was started from.Previously such a command was measured against the working directory of the process, which is
C:\Windows\System32for the service and the shell’s directory fornscp test. The effect was easy to miss because it depended on how the command happened to be spelled: a single backslash (scripts\check_foo.bat) is not tokenisable as an argument vector, so it fell back to the legacy single-string launcher, where the lookup already honoured the installation directory and the command worked. A command that was argv-safe โ a doubled backslash, a forward slash, a quoted path โ ran with argv-isolation, where the executable is named separately and was resolved against the working directory, and it failed for the service with “the system cannot find the path specified”.Check your configuration if either applies:
- You worked around this with an absolute path. Nothing to do โ an absolute path,
a UNC path, a drive-relative
C:check.exeand a root-relative\tools\check.exeare all still used exactly as written. - You relied on the command resolving against the working directory โ a relative command
pointing at a folder outside the installation directory, with the agent started from somewhere
that made it resolve. That no longer works; name the script with an absolute path, or with
${scripts}\<name>if it lives in the script folder.
A command with no folder at all (
cmd.exe,powershell.exe,cscript.exeโ what the shipped[/settings/external scripts/wrappings]use) is not rooted; rooting it at the installation directory would point every one of them at a file that is not there. It now gets the system’s own executable search instead โ the directory the agent loaded from, the working directory, the system and Windows directories, thenPATH. On the argv-isolated path it previously got no search at all and resolved against the working directory alone, socommand = cmd.exe /c โฆonly ran when the agent happened to be started from a directory containing a copy ofcmd.exe. The shipped wrappings were not affected, because their backslash paths put them on the legacy launcher, which has always done the search.Linux is unchanged: the unix launcher sets no working directory for the child and does not root the command, so a relative command still resolves against the agent’s working directory โ which the shipped systemd unit sets to
${shared-path}. - You worked around this with an absolute path. Nothing to do โ an absolute path,
a UNC path, a drive-relative
-
๐ง Check your setup
nscp luaandnscp pynow look in, and import into, the folder scripts are actually loaded from. Nothing to do unless you use those subcommands to manage scripts; loading scripts configured innsclient.iniis unchanged, and existing configuration entries keep working.Both ext-scr CLIs derived their folder by appending a literal
scriptssegment to a root, rather than naming${scripts}โ the token that already means that folder. The two are the same thing only on Windows, where${scripts}is${exe-path}/scripts:Looked in / imported into (before) Now nscp lua list/show/add/delete${base-path}/scripts/luaโ right on Windows,/usr/sbin/scripts/luaon Linux${scripts}/luanscp py list/show/add/delete${scripts}/scripts/pythonโ a folder nothing creates${scripts}/pythonSo on Linux the Lua subcommands were looking beside the binary rather than in the package’s script folder, and the Python ones imported into a doubled path on every platform.
nscpfor external scripts (CheckExternalScripts) was already correct and is unchanged.add --importalso records a different value in the configuration. It used to writescripts\python\<name>/scripts\lua\<name>, which paired with the old folders and only resolved on Windows, where a backslash is a separator. New imports recordpython/<name>/lua/<name>, relative to${scripts}and resolving the same way on both platforms.list(and the script list inshowand the web UI) names its entries the same way, relative to${scripts}:lua/mycheck.luarather than an absolute path or ascripts\lua\mycheck.luathat only resolved from the installation directory. So whatlistprints can be handed straight back toaddfrom anywhere, which is what it is for.Entries written by an older version are not rewritten and keep resolving as before โ the file they point at has not moved.
-
๐ง Remote
[/includes]and[/attachments]are now refreshed on their own. An agent configured from anhttp(s)://settings url re-downloads its whole configuration every settings maintenance interval (/settings/coresettings maintenance interval, default5m). Until now that pass stopped at the top-level file: a file pulled in by[/includes], and every[/attachments]target, was only re-fetched when the top-level file itself happened to change โ so on a server wherensclient.iniis the stable part and the included file is the one that moves, the include stayed pinned to whatever it held when the agent started. Both are now fetched on every pass, and a change in either triggers the same reload a change in the top-level file does.Nothing to configure. Two things to be aware of:
- The settings server now sees one request per included file and per attachment on every interval, not just one for the top-level file. The responses are hash-compared, so an unchanged file still costs nothing beyond the request itself โ but if you serve a large attachment to a large fleet, size the interval accordingly.
- A configuration change made only in an included file now takes effect within one interval instead of requiring a service restart. If you were touching the top-level file to force includes through, that workaround is no longer needed.
-
๐ง The Windows installer creates
scripts\custom\again, and always. Nothing to do โ it is a folder to put your own scripts in, created empty.It used to exist and was lost when a batch of outdated sample scripts was removed. Unlike the shipped examples it is not part of the Scripts (
SampleScripts) installer feature, soREMOVE=SampleScriptsno longer leaves an installation with noscripts\directory at all โ which previously meant an[/attachments]entry, or anything else writing a script, had nowhere to land.Nothing is installed into it, so an uninstall removes it again only if you have left it empty.
-
๐
ext-scr,pyandluaadd --importnow work on a fresh install, and what they record is a path that resolves. Nothing to change: existing configuration is read exactly as before, and this only affects what the import writes from now on.Three things were wrong with importing a script, and each of them was silent in its own way:
Symptom Was Now Import fails outright copy_filenever created the destination folder, so the import died with a bare No such file or directory namingโฆ/scripts/pythonthe folder is created Imported command exits 127 on Linux ext-scrrecordedscripts\<name>โ a Windows spelling of a path this module never resolves anywaythe destination’s absolute path is recorded add --importwith no--scriptthe destination collapsed onto the script folder itself and the copy overwrote that path with a file refused, naming the missing option ${scripts}/pythonand${scripts}/luaonly exist on Windows when the sample scripts feature is installed, and a${scripts}override points wherever an operator chose, so “import into a folder that is not there” was the normal case, not the corner case. The Web UI’s script upload goes through the sameadd --import, so it failed the same way.The command
ext-scr add --importwrites on Linux is now absolute:[/settings/external scripts/scripts] imported = /usr/lib/nsclient/scripts/imported.shThat is not a style preference.
CheckExternalScriptshands the value to the shell verbatim โ no${...}expansion, no search of the script folder โ and on Linux the launcher does not set a working directory for the child at all, so only an absolute path can work. On Windows the child does start in${base-path}with${scripts}directly below it, so the relativescripts\<name>still resolves and is still what gets recorded there.ext-scr list,py listandlua listhad a matching defect: a path that did not begin with${base-path}still had its leading separator sliced off, producing a rootlessusr/lib/nsclient/scripts/check_x.shthat named no file. Those entries are what the Web UI’s script list shows and whatshowis called back with, so they now stay absolute when they are not below the install base.
0.22.0¶
-
๐ ๐ฅ Action required Fleet bundle signatures changed shape: upgrade the fleet server before the agents. Only affects hosts enrolled against a fleet server (
nscp enroll); an installation that does not use fleet management is unaffected.A bundle’s Ed25519 signature used to cover the bare SHA-256 digest of its bytes, which bound nothing about which bundle those bytes were โ an old signed blob could be re-advertised under a new id, name or version and still verify. It now covers a canonical descriptor of the bundle’s identity:
nsclient-fleet/bundle-sig/v2 \0 tenant_id \0 id \0 name \0 version \0 format \0 sha256signed directly (Ed25519 hashes internally), with the fields taken verbatim from the desired-state response. The response therefore carries a new top-level
tenant_id, which the agent needs to rebuild the descriptor and cannot derive from its certificate.The two sides must agree, so:
- Upgrade the fleet server first, or at the same time. A server that signs the descriptor is already re-signing its stored bundles on startup, so no bundle needs re-uploading.
- An agent on this version talking to an older server logs
Bundle <id> failed verification: signature verification failedon every poll and keeps the configuration it last applied. Nothing unverified is applied and nothing already applied is removed, so the host keeps working โ it simply stops picking up changes until the server is upgraded. - An older agent talking to an upgraded server fails the same way, which is the case to watch for if you upgrade the server first and the agents later.
A desired-state response that carries bundles but no
tenant_idis now rejected as malformed rather than failing later as a bad signature, so the log names the real problem.See the security notice.
-
๐ ๐ฅ Check your setup A settings source, include or attachment that is not
https://is now refused. Nothing to do if your agents read a local configuration or use anhttps://settings server. The remote store is the agent’s whole configuration โ[/modules], the external script definitions, the submit clients’ credentials โ re-read at boot and on every housekeeping pass, and over plainhttp://nothing authenticates the server, so anyone on the network path decides what every agent pointed at it runs. All three paths that fetch one (the[settings]url inboot.ini, an[/includes]entry inside a fetched file, and an[/attachments]source) now refuse a url that is nothttps://โ a url with no scheme included โ and log why. An agent that already has a cached copy keeps running on it rather than booting empty. Where plain http is what you want, opt in per host inboot.ini:[tls] allow plaintext = trueEvery plaintext fetch is then logged as
INSECURE.On Windows,
boot.ini’s location is now the${boot-conf}path token rather than a literal baked into the build, so--path-override boot-conf=...relocates it the way the command line already documented and the way it has always worked on Linux. The default is unchanged: next to the executable.See http settings and the security notice.
-
๐ ๐ฅ Check your setup The raw protobuf web API is gone, and agent-to-agent checking now uses the versioned REST API โ which means it works.
POST /query.pbandPOST /settings/query.pbhave been removed. The first handed the agent’s core a message whose header the caller wrote, and the core reads the calling module and user out of that header, so a caller could pick the identity its request was attributed to; the second had been unreachable for several releases. Nothing that talks to an agent over HTTP used them: Icinga’scheck_nscp_apiasks forGET /query/{name}, which is untouched, and the web UI uses/api/v2.Their only consumer was NSClient++ itself, through
NSCPClientโ and that never worked: it passed the serialized message as the HTTP request target, so every request it made was malformed, and the password it read from the configuration was never sent at all.check_remote_nscp,remote_nscp_queryandremote_nscpforwardnow run the command on the remote agent throughGET /api/v2/queries/{command}/commands/execute, authenticating with the samepasswordheadercheck_nscp_apiuses. If you have anNSCPtarget configured, check three things:Setting Was Now path/query.pb(a single endpoint)/api/v2/queries(the collection the command is appended to)TLS off unless ssl = trueon unless no ssl = trueโ the REST API listens on TLSpasswordread, never sent sent, and the remote’s adminuser needs thequeries.executegrantIf you set
pathexplicitly, remove it or point it at/api/v2/queries.verify modestill defaults tonone, because an agent generates a self-signed certificate on first start; pointcaat the issuing certificate and setverify mode = peerwhere you can.exec_remote_nscpandsubmit_remote_nscpare removed rather than ported: both posted an NRPE-stylecommand!arg!argstring to the protobuf route, which the remote parsed as an empty message and answered with nothing โ and the submit path then reported success regardless. The REST API has no endpoint for either, so they now refuse with an explanation instead of discarding results silently. Use NSCA, NRDP or another submit client to send passive results.See the security notice.
-
๐ ๐ฅ Check your setup
check_tcpandcheck_sshnow verify the server certificate by default.verifydefaulted tonone, so a TLS check completed its handshake against any certificate at all and reportedok. It now defaults topeer, andca=defaults to the agent’s own trust bundle (${ca-path}, the same onecheck_httphas always used), falling back to the trust store OpenSSL was built with when that is empty. Checks against publicly issued certificates start validating out of the box; checks against a self-signed or internal certificate that used to reportoknow reporttls_handshake_failed.- If you check an internal or self-signed service, either point
ca=at the issuing CA โ a PEM bundle or a hashed directory both work โ or addverify=noneto keep the previous behaviour. The certificate keywords (ssl_expiry_days,cert_cn,cert_sans,cert_verify, โฆ) are readable either way: they are properties of what the peer served, not a trust decision. - Only TLS checks are affected, which means
ssl=true, astarttls=protocol, or one of the implicit-TLSservice=presets (spop,simap,ssmtp). A plaincheck_tcpis unchanged. sni=is now rejected when the connection is not TLS, instead of being silently ignored. It named the certificate to ask for and to verify against, so without TLS it asserted nothing while still reportingok.sans=on a plain connection now reportssan_missingrather thanok. A connection that served no certificate covers no required name โ which is what forgettingstarttls=on a submission port looks like.
Both commands are marked experimental, which is the flag that reserves exactly this: their options, keywords and output may still change while the shape of the check settles.
See the security notice.
- If you check an internal or self-signed service, either point
- ๐ Check your setup A request may no longer weaken the transport security of a credentialed
target, or re-address
submit_smtpmail. The client override guard now covers the keys that decide how a connection is protected โverify,insecure,insecure-skip-verify,no-psk,security,ssl,no ssl,tls-version,ca,certificate,certificate-key,allowed-ciphers,dhโ and, for SMTP,recipientandsender. Nothing to do unless callers pass one of those to a target that carries a password or token; repeating what the target configured is still fine, and the remedies are the same as forhost=: supply the credentials with the request, configure the variant as its own target and select it withtarget=, or setallow host override = true. The addressing keys have their own, narrower opt-in,allow recipient override = true.payload-length/buffer-lengthare clamped to what each protocol accepts, 65536 for NSCA and 1 MiB for NRPE, so an absurd value can no longer allocate gigabytes while every supported payload size keeps working. A failed CA load reports a generic message with the path and reason in the log. See the security notice.
- ๐ Check your setup A request may no longer reroute a credentialed NRDP target through its own proxy.
The client host-override guard now treats
proxy=andno-proxy=as moving the request, since a caller-chosen proxy receives it whole, configured token included (see the security notice). Nothing to do unless callers passproxy=onsubmit_nrdpagainst a target that carries a token; repeating the configured proxy is still fine, and the remedies are those ofhost=โ pass the token with the request, configure the proxied route as its own target and select it withtarget=, or setallow host override = true.
- ๐ Check your setup The fleet certificate pin is enforced as a pin. A pinned PEM that is the
server’s leaf certificate is now matched by its public key at every handshake;
one that is itself a CA keeps hostname verification, because a CA certificate
cannot identify a server on its own โ previously either kind was simply trusted
for any name. If enrollment handed out a CA rather than the leaf, that
connection now requires the certificate to match the name in
mtls_url. A pinned PEM that does not parse is refused rather than quietly falling back to ordinary verification. See the security notice.
- ๐ Check your setup
host=on a docker check must now match the configured endpoint. Which daemon the agent talks to is decided byendpointunder[/settings/docker]; a request repeating that value is accepted, one naming a different socket or pipe is refused. The agent would otherwise connect to any local socket or\\.\pipe\<name>a caller named and report back what happened, which is a read-only probe of the host asSYSTEMorroot. Check definitions that spell outhost=keep working as long as they name the configured endpoint; otherwise drop the argument or change the setting. See the security notice.
- ๐ Check your setup A NUL in a script argument is refused, and
ext-scr add --importreads only from the script folders.CreateProcessWreads the command line as a C string, so an argument containing a NUL truncated it and silently dropped every operator-fixed argument after the substitution point; that is refused now whateverallow nasty characterssays, because it changes what the launcher was asked to run rather than what the script sees.add --importcopied from any path the service account could read into the script folder, whereshowthen returned the bytes โ which made the show/delete sandbox hold only until someone carried a file inside it. Import sources are confined to the script root,${shared-path}and the upload staging area; copy a script into one of those first if a workflow relied on importing from elsewhere.PUT /api/v2/scriptsis unaffected. On Windows, a script that prints in exact buffer-sized chunks no longer parks a worker thread past the timeout, and a timed-out or forked script no longer leaks a process handle. See the security notice.
- ๐ External scripts no longer inherit the service’s handles. A script now receives only its own stdin and stdout/stderr pipe ends: on Windows through an explicit inherit list (spawns are serialised on the XP build, which has no such list), on Unix through close-on-exec pipes and a close of every descriptor above stderr in the child. Previously a concurrently running script’s output pipe, and every other inheritable handle of the service, crossed into each child (see the security notice). Nothing to do.
-
๐ Check your setup External scripts with
user,domainorpasswordset are refused on Linux. The run-as keys of a script section are implemented by the Windows launcher only; the Linux launcher ignored them and ran the script as the service account (root on a manualnscp servicerun), so a script sandboxed withuser = nobodywas not sandboxed at all. Such a command now returns UNKNOWN with a message pointing atsudoand the script does not run. If you set these keys on Linux, remove them and put the identity change in the command itself, granting it insudoers:[/settings/external scripts/scripts/check_as_nobody] command = sudo -n -u nobody /usr/lib/nagios/plugins/check_somethingWindows is unaffected. See the security notice.
- ๐ Check your setup The web server sends browser hardening headers, can negotiate TLS 1.3 on
Linux, and stops minting a session token per request. Every response now
carries
X-Frame-Options: DENY,X-Content-Type-Options: nosniff,Referrer-Policy: no-referrerand a content security policy, plusStrict-Transport-Securityover TLS โ so a page that embeds the agent’s UI in a frame will stop working, which is the point.[/settings/WEB/server]gainstls version(default1.2+) andallowed ciphers, honoured on the beast backend that every Linux package uses; it was pinned to TLS 1.2 only before, and ignored both keys. The vocabulary is the one the NRPE and NSCA listeners use, with one exception:tls version = sslv3is refused rather than started, because this listener never serves SSL 3.0 and pinning the range to it would mean a listener that accepts no handshake at all. A value this listener cannot honour โ an unknown version, or a cipher list OpenSSL rejects โ stops it starting rather than silently leaving the defaults in place. The mongoose backend cannot apply either key and logs that it is ignoring a value the operator set; it stays quiet about one left at its default. A token is issued only by the login routes now, so a monitoring poll authenticating with Basic auth no longer fills the 4096-entry store and evicts live UI sessions โ a script that logs in through Basic auth and reuses a token must take it fromGET /api/v2/login. The session credential is no longer echoed back as a cookie nothing read, and the UI keeps its bearer insessionStorage, so a new browser tab asks for credentials again./api/v2/scripts/<runtime>accepts onlyext,pyandlua(which now works); anything else is 400. See the security notice.
- ๐ The web server’s in-memory log buffer is capped, logging out revokes the
session token, and
nscp web install-uino longer stages its download in${temp}. Nothing to do. The log view keeps the newest 1000 entries instead of growing without bound (an unauthenticated peer could previously add one entry per rejected request, for the life of the process); the error count on the badge still counts every error the agent reported. Logging out of the web UI now callsDELETE /api/v2/login, so the bearer token stops working immediately rather than at the end of its eight-hour life โ worth knowing if you have a script that logs in, logs out, and then reuses the token.nscp web install-uiverifies the bundle in memory and writes it once into a private directory under the web path, so nothing lands in the shared temp directory. See the security notice.
- ๐ง ๐ Check your setup
nscp nrpe installwritesuse ssl, and the insecure preset’s cipher string says what it is. The install command used to writessl = true, a key the server never reads: on a host whereuse ssl = falsehad been set previously, it claimed encryption and client-certificate authentication while the listener stayed in plaintext. Check that value if you ran the command on such a host. The legacy (insecure = true) cipher default drops its!ADH, which never excluded the anonymous elliptic-curve suites and could not have excluded them without breaking a mode that loads no certificate; the string now matches whatnrpe install --insecurewrites. The secure default gains!aNULLbeside!ADH, which changes nothing in practice โ OpenSSL’s default security level already refuses anonymous suites. See the security notice.
- ๐ง ๐
allowed hostsfinally understands the*ranges it has always advertised.192.168.1.*means192.168.1.0/24,192.168.*means/16,10.*means/8, and a bare*is every address. Until now such an entry threw out of the address parser and โ with the defaultcache allowed hosts = trueโ took the whole module with it, so the listener never started. A*in the middle of an address (192.*.1.1), or one combined with an explicit/mask, is reported as a configuration error, and an unparseable numeric entry is now an error beside the others rather than an exception. Nothing to do; if you have been avoiding*because it stopped a listener, it works. See the security notice.
-
๐ Check your setup The shared
/settings/default/passwordis always redacted, and followsuse credentials. Nothing to do on a default install. The key is now registered sensitive by the core rather than by whichever ofWEBServer,NSCAServerandNSClientServerhappens to be loaded, sonscp test’ssettingsdump,nscp settings --list/--showand the REST read paths answer***for it on every agent instead of only those running one of those modules. Read the value out ofnsclient.iniwhen you need the secret itself. Two consequences if you rely on the old behaviour:- Tooling that scraped the shared password back out of one of those listings
now receives
***. - On Windows with
use credentials = true, the next write of the key (anscp settings --update, or any change made through the REST or CLI settings writers) moves it into the Credential Manager and leaves a reference innsclient.ini, the same as every other sensitive key. Agents that do not enable credentials are unaffected, and the key is not rewritten until something writes it.
See the security notice.
- Tooling that scraped the shared password back out of one of those listings
now receives
-
๐ ๐ง Check your setup A round-robin counter with a zero buffer size is now refused. From the security notice; nothing to do unless you have one of these:
- A Windows counter with
collection strategy = rrdandbuffer size = 0, or abuffer sizethat does not parse, is skipped and named in the log instead of being loaded with a buffer that holds nothing. Remove the setting to get the 60m default, or set a real window. - A Lua binding given a word where it expects a number reads it as
0rather than terminating the agent.Settings():get_int(path, key, "n/a")returns the key’s value with0as the default, where it used to be fatal. - A Python string that cannot be encoded as UTF-8 comes back with the offending characters replaced instead of crashing the agent.
- A Windows counter with
- ๐ An Elastic or Op5 submission over an unverified
httpslink now says so in the log. When the endpoint’sverify moderesolves to no peer verification and credentials are configured, both modules log a message naming the endpoint โ the credentials then go to whichever server answers โ matching what the Icinga, NRDP and NRPE clients already do. It is logged once per module load, and the connection itself is unchanged. The Icinga client’s own warning was repeated on every submission and is now logged once per target instead. The shared--verify,--ca,--certificate-keyand--allowed-ciphershelp texts (NRPEClient,NSCPClient,NSCAClient,CheckMKClient,NSCANgClient) were corrected at the same time: three of them read “Client certificate format”. See the security notice.
- ๐ Check your setup The DEB and RPM packages now install
nsclient.iniand the log directory owner-readable./etc/nsclientbecomesroot:nsclient 0750,/etc/nsclient/nsclient.iniroot:nsclient 0640,/var/log/nsclientnsclient:nsclient 0750and the log file0640; they were world-readable, and the configuration file holds the web admin password, the NRPE/NSCA passwords and the submit-client tokens in plaintext. The post-install scripts apply this on upgrade as well, so an existing host is fixed by upgrading. If a local script or a monitoring user reads either path without being root or in thensclientgroup, add it to that group first โ otherwise it will start getting permission denied. Windows is unaffected. See the security notice.
- ๐ The Windows release build pins every third-party action to a commit and
verifies what it downloads. Nothing to do โ this concerns how the released
artifacts are built, not an installed agent. Third-party actions are pinned to
commit SHAs (one ran from a branch, in the same job that holds the code-signing
credentials), Google Test is checked against the commit its tag names, and every
downloaded dependency goes through
.github/dependency-checksums.txt: a mismatched digest fails the build, and so does bumping a version without recording one. See the security notice.
-
๐ฅ ๐ Check your setup
/api/v2/openmetricsnow serves a conformant, self-describing OpenMetrics document, and every metric name changes. Only affects scrapes of/api/v2/openmetrics. The JSON endpoints (/api/v2/metrics,/metrics), the web UI dashboard, Graphite, collectd and Pythonsubmit_metricsreport the same keys and the same values as before โ the metric key is untouched and stays authoritative for all of them.The endpoint used to paste the JSON keys into the exposition verbatim, so names carried
.,%, spaces and colons (system_mem_commited.avail,system_cpu_core 0.idle,disk_free_C:.total), there was no# TYPEand no# EOF, values were truncated to six significant digits โ 16 GB of memory scraped as1.6554e+10โ no module declared what any of its readings meant, monotonic counts were typed as gauges sorate()was unsafe on them, and string-valued metrics (uptime, boot time, MAC address, power source) were dropped entirely. A strict parser rejected the body, and the scenario page told you to repair the names withmetric_relabel_configs.Three things changed, and each of them renames families.
1. Names are rewritten to the OpenMetrics grammar.
JSON key Metric name system.mem.physical.%system_mem_physical_percentsystem.cpu.core 0.idlesystem_cpu_core_0_idledisk.free.C:.totaldisk_free_C_total%becomes the wordpercent, everything else outside[a-zA-Z0-9_]becomes_, runs collapse to one, and a name that would not start with a letter borrows ametric_prefix (a leading underscore is reserved). Every family carries a# TYPEline, the body ends with# EOF, values keep their full precision, and the response is typedapplication/openmetrics-text; version=1.0.0when the scraper asks for it.2. Every metric carries a description, a type and a unit, and declaring a unit renames its family again:
# HELPon every built-in family, and# UNITwherever the value is measured in something.- Counters are typed as counters โ
workers.{jobs,submitted,errors},scheduler.{jobs,submitted,errors},system.process_history.<exe>.times_seenand the real-time filter counts. Their sample carries the_totalsuffix the specification reserves for them. - Strings come back as an
_infofamily, thenode_uname_infoshape:system_uptime_info{uptime="1d 12:30",boot="2026-09-13 01:15"} 1.
A family that declares a unit has to end in it, so the name the key alone would give gains a suffix:
Name from the key alone Name it is served under system_mem_physical_totalsystem_mem_physical_total_bytessystem_cpu_total_idlesystem_cpu_total_idle_percentsystem_uptime_ticks_rawsystem_uptime_ticks_raw_secondsdisk_free_C_totaldisk_free_C_total_bytesworkers_jobsworkers_jobs_totalA name that already ends in its unit keeps it, which covers every
.%key (system_mem_physical_percent) and the clock frequencies. A per-second rate declares no unit and gains no suffix either (system_network_eth0_received,disk_io_sda_read_bytes_per_sec).3. Per-instance metrics are one family with a label, so every per-core, per-NIC, per-drive and per-process family name changes again. Rewriting the key alone would leave one family per core, which is what the endpoint has always served: the family names then depend on how many cores a host has,
sum by (core)has nothing to group on, and a Grafana variable has no label to bind to.# one family per core, as a name-only rewrite would leave it system_cpu_core_0_idle_percent 93 system_cpu_core_1_idle_percent 91 system_cpu_total_idle_percent 95 # what is served # TYPE system_cpu_idle_percent gauge system_cpu_idle_percent{core="0"} 93 system_cpu_idle_percent{core="1"} 91 system_cpu_idle_percent{core="total"} 95The same move applies to every producer with an instance in its key:
Bundle Label Value system.cpucore0,1, โฆ andtotalfor the aggregatesystem.cpu_frequencycputhe sysfs core on Linux; the processor DeviceID(CPU0) on Windowssystem.networknicthe interface as the OS names it โ the adapter description on Windows system.temperaturezonethe thermal zone or sensor system.batterybatterythe battery; absent for a single unnamed battery system.process_historyexethe executable name system.metricspdh_instancethe PDH instance, for a counter configured with instances disk.iodiskthe device disk.freedrivethe drive or mount point pdh_instancerather thaninstance: Prometheus attaches its owninstancelabel (the scrape target) to every sample, and under the defaulthonor_labels: falsean exportedinstanceis renamedexported_instance, so a query againstinstancewould match the host rather than the counter.The
_infofamilies gain the same labels, which is what splits them per instance: one NIC’s link state, MAC address and speed now share a series keyed bynic="โฆ", instead of every NIC’s strings piling onto one series under label names likeeth0_status. Windows and Linux spell a CPU core differently in the JSON key (core 0andcore_0); neither spelling reaches the label, which is the bare0on both, so one query works across a mixed fleet.What to do:
- Drop any
metric_relabel_configsblock that rewrote dots to underscores. The agent does exactly that itself now, so the rule no longer matches. - Update dashboards, recording rules and alerts that name the old series.
Every name moves at least once, and a per-instance one turns into a label:
system_cpu_core 0.idlebecomessystem_cpu_idle_percent{core="0"},disk_free_C:.totalbecomesdisk_free_total_bytes{drive="C:"}andworkers_jobsbecomesworkers_jobs_total. Most of them get shorter, and a query that used to enumerate instances can now aggregate. - Exclude
core="total"from anything that aggregates over cores. It is the all-cores aggregate, mirroring thesystem.cpu.total.*JSON key, sosum without (core) (system_cpu_idle_percent)double-counts. Writesystem_cpu_idle_percent{core!="total"}instead. - Check what you are taking a
rate()of. Only the counters listed above are monotonic.system_network_<nic>_totalis a per-second rate andsystem_os_updates_countis how many updates are pending right now; both go down again, and arate()of either was always nonsense โ it is just visible now that the honest counters are labelled as such. -
If dashboards cannot be updated before the upgrade, set
[/settings/WEB/server] openmetrics format = legacywhich reproduces the pre-0.21 body byte for byte, with no metadata at all. It is deprecated and will be removed in a future release, so use it as a migration window rather than a setting to leave in place.
Two keys can now want the same metric name (
mem.used.%andmem.used percentboth render asmem_used_percent). The first metric of the snapshot keeps the name and the rest are dropped โ emitting both would be the same series twice, which costs the scraper the whole body rather than one metric. Each distinct collision is logged once (again after a settings reload) naming the metric that was dropped and the name it collided on. Nothing shipped produces such a pair; a predefined PDH counter or a Python script can.Smaller changes that come with it:
-
Windows only:
system.mem.page.%andsystem.mem.physical.%report different numbers now, because they were reporting the wrong thing. Both divided the commit charge by the commit limit while checking their own total for zero, so they published the commit figure under a page-file and a physical-memory name โ and divided by zero on a machine that reported a page file or physical memory but no commit limit. Each reads its own numbers now. The JSON keys and the metric names are unchanged; only the values are, and they were wrong before. An alert threshold tuned against the old reading needs re-checking. -
The two negotiated bodies are no longer identical. The endpoint already answered
application/openmetrics-text; version=1.0.0ortext/plain; version=0.0.4depending on the request’sAcceptheader. The two specifications disagree about what a counter’s metadata lines name โ the family in OpenMetrics (# TYPE workers_jobs counter, sampleworkers_jobs_total), the sample in the older format (# TYPE workers_jobs_total counter) โ so the agent renders both and serves the one matching the type it answers with. Sample lines are identical in both. Nothing to do; a scraper that negotiates gets the richer form as it always did. -
Predefined PDH counters can describe themselves.
[/settings/system/windows/counters/<name>]takes two new optional keys,helpandunit, which become the# HELPand# UNITlines of that counter’s metric.helpdefaults to the counter path. Nothing else reads them, and a counter that sets neither behaves exactly as before. -
Python
fetch_metricsaccepts a dict per value.{"value": 42, "help": "โฆ", "unit": "bytes", "type": "counter"}in place of a bare number, and"labels": {"queue": "inbound"}to label a metric. Plain numbers and strings keep meaning exactly what they did, and an out-of-tree C++ module callingnscapi::metrics::add_metric()is unaffected. -
/api/v2/metrics?meta=1serves the same keys and values with their help text, unit, type and labels under ametadataobject, for a dashboard that wants to print a unit rather than a bare number. Withoutmetathe endpoint is byte for byte what it was. -
GraphiteClient can send the labels as carbon tags.
metric tags = trueon a target appends;core=0to the metric path, which is otherwise unchanged. Off by default: a carbon older than 1.1 has no tag support and storespath;core=0as the metric name. -
CollectdClient mappings can read the labels and the types. A variable set to
label:coreexpands to every distinct value of that label, instead of a regular expression over the flat keys that has to know whether the platform spells a corecore 0orcore_0; and a metric expression spelledauto:sends whatever the producing module declared a counter as a collectd DERIVE and everything else as a GAUGE. The built-in default mappings are unchanged. Note that a label includes the aggregates the exposition labels โlabel:coreyieldstotalalongside0,1, โฆ โ so a template built from it can name a metric that does not exist, which is skipped rather than sent. -
A collectd value list naming a metric the snapshot does not carry is no longer sent as a zero. The value expression resolved a missing key to an empty string and forwarded the
0that parsed out of it, so a mapping naming a metric this platform or configuration never produces reported a measurement nobody took โ which is what the platform-specific default mappings exist to avoid. Such a value list is now skipped, in whole: a collectd value list is positional, so dropping one value of several would have the receiver read the next metric’s number under this one’s type. Aderive:expression also splits on,likegauge:always did, instead of looking the wholea,bstring up as one key and sending a single zero. Only hand-written mappings are affected, and only where they were reporting zeroes.
See the REST metrics reference and the Prometheus scenario for the full rules.
-
๐ฅ New module:
GearmanClient, a Mod-Gearman worker and result channel. Nothing to do on an existing install โ the module is optional and disabled by default. Enabling it lets a Naemon or Nagios Core keep scheduling its checks while this agent pulls them off agearmandjob server and answers them as native queries, with no inbound port on the monitored host. It speaks both flavours of the protocol: ConSol’s Mod-Gearman (Naemon) and Nagios Enterprises’ Nagios-Mod-Gearman (Nagios Core 4.5+).Two ways to deploy it, both configured under
/settings/gearman/worker:mode = agent(the default) answers only for the host it runs on; a job for any otherhost_nameis answered UNKNOWN rather than executed.mode = proxyanswers every check on the queues it registered, whichever host the core meant it for โ one domain-joined Windows box running the checks of a whole hostgroup throughcheck_nrpe,check_wmiand the rest. A proxy reaches everything its own credentials reach, so keep the key to those queues to the hosts that should have it.
The same module submits passive results into the core’s result queue (
/settings/gearman/client, channelGEARMAN, commandsubmit_gearman), which is what lets a Mod-Gearman installation drop NSCA. The two halves are independent: configure either one and leave the other empty.Two things a Nagios operator usually has to unlearn when writing the
check_commandon the core, since neither fails in a way that names the cause:- write
host=$HOSTADDRESS$, not-H $HOSTADDRESS$โ a two-character first token puts the agent’s argument parser into key-value mode and the check answers with a help screen; - write
warning=load gt 80, notwarning=load>80โ>is a metacharacter andallow nasty charactersisfalseby default, as in NRPEServer.
On Windows the module is part of the Various client plugins feature of the installer. See Mod-Gearman for the full setup on either core, and Securing NSClient++ for the shared-key model.
The module is marked experimental, and beyond the usual meaning of that mark โ settings, queue handling and output may still change โ it has not been tested at scale. It is verified end to end against a real
gearmandon both cores, but not against hundreds of hosts, a deep job backlog, or a proxy answering for a large hostgroup. Roll it out to a slice of the estate first and keep the transport you have until you are satisfied.
-
๐งช New modules and check commands are now marked experimental. Nothing to do on an upgrade: every check keeps working exactly as before. A module or check command that is new enough that its options, filter keywords and output may still change now says so โ
nscp testappends(experimental)to the name inqueries,aliases,listandplugins(and shows aStatus:line indesc), the web UI shows an Experimental chip on the module and query pages, the REST API reports it as anexperimentalfield on modules, queries and aliases, and the reference documentation renders a marker in the command tables plus a note on the command itself.The recently added
check_*commands ofCheckDisk,CheckDocker,CheckNet,CheckNSCPandSchedulerare marked, as are theCheckMSSQL,CheckMySQL,CheckSecurity,CheckWindowsApps,GearmanClientandNSCANgClientmodules in full.The marker is not a warning that a check is broken โ it is a statement about stability: pin the options and syntax you depend on, and re-read the command’s reference page after an upgrade. Marks are removed as the commands settle.
If you build your own modules, declare it in
module.jsonโ"experimental": trueinside"module"for the whole module, which covers every command it registers, or on a single command entry when only that check is new:"commands": { "check_new_thing": { "description": "โฆ", "experimental": true } }
-
๐ฆ Check your setup Common code moved out of the individual plugins into shared libraries. Code that every plugin used to compile privately now lives in one place, so the install is smaller: about a fifth on Debian and RedHat, and on Windows every TLS-capable module sheds the private copy of OpenSSL that used to make up most of it. New libraries ship next to
nscp.exe:nscp_net.dll(socket and TLS helpers),nscp_client.dll(the client-side command line handling the sender modules share),nscp_json.dll(Boost.JSON), and OpenSSL itself aslibcrypto-3-x64.dllandlibssl-3-x64.dll(libcrypto-3.dllandlibssl-3.dllon 32-bit) โ one copy for the whole service, where before each of NRPE, NSCA, check_mk, the web server, the HTTP clients and the checksum checks carried its own.plugin_api.dllalso grew, having taken over the settings helpers and the program-options helpers that each plugin used to carry its own copy of. The legacy Windows XP build is not affected: it still links everything statically.Nothing to do when installing from the MSI or from the Debian/RedHat packages โ they install the new libraries for you, and the modules themselves are unchanged from the outside. Only a hand-rolled deployment needs to check: if you copy
modules\*.dllinto place yourself rather than installing a package, copy the new libraries (the OpenSSL DLLs included) from the installation root as well, or the modules that need them will fail to load.
-
๐ฆ Windows on ARM is packaged again, without the Python module. Releases carry
NSCP-<version>-ARM64.msiandNSCP-<version>-ARM64.ziponce more; ARM64 was built for one release in April 2026 and then withdrawn because the build failed. Nothing to do on x64 or 32-bit Windows, and an ARM64 machine running the x64 package under emulation keeps working โ the native package is simply faster.One difference from the x64 package: the ARM64 build does not include
PythonScript, nor the embedded Python runtime that comes with it. The package is cross-compiled on an x64 runner and there is no ARM64 CPython to embed, so the module and itspython*.dll/python*.zipare absent from the MSI and the Python Scripting feature does not appear in the installer. An ARM64 host that needs internal Python scripts should run the x64 package under emulation.CheckExternalScriptsis unaffected and can still run a system-installedpython.exe.One more difference, and this one needs action on a fresh machine: the ARM64 MSI does not install the Visual C++ runtime. The x64 and 32-bit installers bundle it as a merge module, but Microsoft ships no ARM64 merge module for the toolset this is built with, so there is nothing to bundle. Install the Visual C++ Redistributable for ARM64 before the agent, or the service will fail to start with a missing
vcruntime140.dll. Most machines already have it โ anything that has run a recent ARM64 desktop application will โ and upgrades over an existing install are unaffected.
-
๐ฆ A Raspberry Pi OS package. Releases now carry
NSCP-<version>-debian-trixie-arm64.deb, built on Debian 13 (Trixie) โ the base of current Raspberry Pi OS 64-bit โ for Raspberry Pi 3 and newer, and for Debian 13 arm64 in general. Nothing to do on an existing install; the Ubuntu packages are unchanged and keep working where they already do.There is no 32-bit package. Raspberry Pi OS has defaulted to 64-bit since Bookworm, and a 32-bit build would have to be either a slow emulated one or a Debian
armhfpackage that still leaves the ARMv6 boards (Pi 1, Pi Zero W) out. On a 32-bit Raspberry Pi OS install, either reimage to 64-bit or build from source.The Debian package is built without the .NET SDK, which Debian does not package, so it ships without the managed (C#) plugin API. The Ubuntu packages still include it.
- ๐ง The web bundle is built with
npm ciand gated onnpm audit.npm installtolerates a lockfile that has drifted frompackage.jsonand rewrites it, so the bytes in the web zip and in the MSI’s bundledweb/distwere not guaranteed to matchpackage-lock.jsonโ the.sha256manifest the installer verifies proved integrity in transit, not provenance of the dependency tree.npm cifails on drift instead, and a new step runsnpm audit --omit=dev --audit-level=highso a known-vulnerable runtime dependency stops the release build. Nothing to do; contributors whosepackage-lock.jsonis out of date will see the build fail rather than see it quietly rewritten.
-
โฑ๏ธ ๐ง Check your setup Reloads now wait for the checks that are running, and a module can no longer unload or restart itself from inside a request it is serving. Nothing to do on a default install; the changes are visible only in a few specific setups.
- A settings reload holds new checks off a module while its
loadModuleExapplies the new configuration, and waits up to five seconds for the checks already inside it to return. A reload is therefore no longer instantaneous on a busy agent, and a check arriving during one waits rather than running against half-applied settings. If a check holds a module for longer than five seconds the reload proceeds anyway and logs which module it was. - Unloading the module that is serving the request is refused and answers with
an error. In practice this is
POST /api/v2/modules/WEBServer/commands/unload(or the same throughput_moduleor the console), which previously took the agent down with it. UnloadWEBServerfrom the command line or restart the service instead. - Reloading a listener module from inside a check that the same listener is
serving - a script calling
core.reload("NRPEServer")from a check invoked over NRPE - is refused with an error instead of leaving that listener dead. Reload it from another transport, or reload the service as a whole. A reload that does not touch the module serving the request is unaffected and still applies before the call returns. - Configuration downloaded over HTTP (
[/settings] 1 = http://...) now gives up on a read or write that stalls for 30 seconds instead of waiting indefinitely. A settings server that used to take longer than that to answer will now fail the refresh and keep the cached copy. - Submissions over NRPE, NSCA, NSCP and check_mk apply the configured timeout to connecting and to the TLS handshake, not just to the exchange. A target that accepts a connection and then goes quiet now fails its timeout instead of holding a scheduler thread.
- A settings reload holds new checks off a module while its
-
๐ฅ Check your setup
check_ntFILEAGEnow checks one file instead of a directory of them.FILEAGEanswers with a single age, taken from the first performance value the underlying check produced. The legacy request mapped ontocheck_files, which walks a whole tree, so a directory argument reported whichever file the walk happened to emit first โ not the oldest, not the newest, just arbitrary. The number looked authoritative and meant nothing.The request now maps onto
check_single_file, which stats exactly one path, so there is only ever one candidate and the age is defined. AFILEAGEnaming a single file, which is what the check was always for, is unaffected and reports the same age as before. AFILEAGEnaming a directory now fails with an error instead of returning an arbitrary file’s age, and one naming a missing file reports that it was not found. If a check depends on passing a directory, point it at the file it actually means.check_single_filelives inCheckDisk, ascheck_filesdid, so no extra module needs enabling.
-
๐ง Check your setup The Windows installer no longer rewrites NRPE transport security it did not configure. The MSI offers two NRPE presets, insecure and secure, and used to apply one whenever it could not recognise the mode an existing or imported configuration was in. It recognised only
insecure = trueandverify mode = peer-cert, so two perfectly ordinary setups fell through:- A listener running TLS without client certificates (
verify mode = none).msiexec /i NSCP-โฆ.msi IMPORT_CONFIG=โฆcame back with four keys under[/settings/NRPE/server]that the imported file never asked for:verify modeforced topeer-cert,tls versionlowered totlsv1.2+, plusinsecure = falseand an emptyssl options(#1558). - A listener configured without either key โ
port = 5666and nothing more, running onNRPEServer’s own defaults. An upgrade read that as “nothing configured here” and wrote the secure preset, so a listener that had never required client certificates started demanding them.
A configuration whose mode was recognised kept its mode, but still had
tls versionandssl optionsrewritten.The installer now reads what the configuration says and applies a preset only where it would overwrite nothing: a fresh install with no NRPE listener configured, or an operator who moves the radio button or passes
NRPEMODE=LEGACY/NRPEMODE=SECUREon the command line. It also no longer writestls versionorssl optionsat all โ both were being set to the valueNRPEServeralready defaults to, so they added nothing to a new install while overwriting an explicit choice on an existing one.Check
[/settings/NRPE/server]after upgrading if you install or upgrade on Windows with an NRPE configuration of your own, in particular one imported withIMPORT_CONFIG:verify modeis now left as your configuration has it, and is no longer added where you never set it. If an earlier install silently moved you topeer-certand you have since issued client certificates, keep it by leaving the setting in your configuration โ it will not be re-added for you.tls versionis left alone, so a pinnedtls1.3survives the install instead of being replaced withtlsv1.2+.- A fresh install is unchanged: it still gets
verify mode = peer-cert, orinsecure = truewithNRPEMODE=LEGACY.
- A listener running TLS without client certificates (
-
๐ง Check your setup The PDH counter browser now narrows on every filter, and ignores case. Nothing to do unless you have a script or a habit built on the counter browser (
nscp sys -- --list โฆ, orexec CheckSystem --list โฆat thenscp testprompt). Two things changed about which counters it lists:--list,--filterand--counterused to share one variable, so whichever came last won and the others were silently discarded:--list SQL --filter Databaseslisted everything matchingDatabases, including counters with noSQLin them at all. Every filter now has to match, so the same command lists what matches both โ a narrower result than before.--filteris also repeatable now, so a listing can be cut down one word at a time.- Matching is now case insensitive, where it used to be case sensitive.
--list diskused to find nothing at all; it now finds the same counters as--list Diskโ a wider result than before. PDH capitalises its own names inconsistently and a localised Windows spells them in another language, so having to guess the casing before getting any output was a trap rather than a feature. (The fold is ASCII: the non-ASCII part of a localised name still has to be typed as it is spelled.)
nscp sys -- --list disk --all --filter queue --filter avg. \PhysicalDisk(_Total)\Avg. Disk Queue Length \PhysicalDisk(_Total)\Avg. Disk Read Queue Length \PhysicalDisk(_Total)\Avg. Disk Write Queue LengthUnchanged otherwise: a substring test against the whole
\object(instance)\counterpath, and a--filterwith no value is still accepted and still filters nothing.
0.21.0¶
-
๐ ๐ง The checks whose argument decides what data is read can now be told which files, queries and counters a caller may ask for. Nothing to do on upgrade: they all default to
any, which is exactly what earlier releases did. Those checks take an argument that decides what data is read, and the agent reads it with its own privileges โ so where callers choose the argument (NRPE withallow arguments = true, or a REST user not on the no-argumentsrestrictedrole) an unrestrictedfile=is a general file-read primitive. Each module gained a mode setting and an allow list:Check Section Mode setting Allow list check_logfile[/settings/logfile]file accessallowed filescheck_wmi[/settings/wmi]query accessallowed classes,allowed namespacescheck_pdh[/settings/system/windows]counter accessallowed counterscheck_files,check_single_file,check_disk_write[/settings/disk]file accessallowed filescheck_registry_key,check_registry_value[/settings/system/windows]registry accessallowed registry keyscheck_eventlog[/settings/eventlog]log accessallowed logsThe modes are
any,allowed(only what matches the list; experimental, since it parses what the caller sent) andpredefined(the secure option: only names you configured โ[/settings/logfile/files],[/settings/wmi/queries],[/settings/disk/files],[/settings/system/windows/registry],[/settings/eventlog/logs], and forcheck_pdhthe counters already in[/settings/system/windows/counters]). Registry and event-log entries are hierarchical: an entry covers that key or channel and everything below it, matched on whole name segments. Configured names resolve in every mode, so you can name your checks first and tighten the mode afterwards. Some arguments only tighten once a mode is set: acheck_wminamespace=may then no longer be moved offroot\cimv2unlessallowed namespacessays so,check_wmitarget=must name a target defined in[/settings/wmi/targets],check_registry_*refusescomputer=so only the local registry is read, andcheck_eventlog’s defaultApplication/Systemchannels go through the gate like any other. See Restricting what a check may read, the securing guide and the security notice.
-
๐ ๐ง Check your setup A new built-in WEB role,
metrics, and amonitoringrole that can finally scrape. The metrics endpoints are gated bymetrics.list(/api/v2/metrics) andopenmetrics.list(/api/v2/openmetrics), but the bundledmonitoringrole grantedmetrics.getโ a privilege nothing checks, so amonitoringuser was answered403on both endpoints.monitoringnow grants the two real ones instead:[/settings/WEB/server/roles] monitoring = public,queries.execute,aliases.list,login.get,metrics.list,openmetrics.list metrics = public,metrics.list,openmetrics.list,login.getThe new
metricsrole is for a Prometheus scraper: it reads the two endpoints and holds noqueries.execute, so it cannot run checks.Roles already written to
nsclient.iniare never rewritten, so an existing install keeps the role strings it has โ including amonitoringline still carrying the inertmetrics.get. Update that line by hand (or assign the newmetricsrole) if you want those users to scrape. Nothing widens by itself on upgrade; see the security notice.
- ๐ Check your setup Fleet: encrypted bundles are now opened by the agent. Nothing to do unless
you use the fleet server’s Encryption key feature. A bundle the operator seals
in the browser (
format: enc-v1) used to be refused by the agent as an unreadable archive; the agent now opens it with a key you hand it out of band, and the server never sees that key. Give the key at enrollment withnscp enroll --bundle-key <key>or theFLEET_BUNDLE_KEYinstaller property, or to an already enrolled host withnscp enroll --update-bundle-keys --bundle-key <key>(several--bundle-keywhile rotating; on Windows, re-running the installer with onlyFLEET_BUNDLE_KEYdoes the same). The key is stored in the enrollment manifest (agent-state.json) beside the host’s private key, never innsclient.ini.nscp enroll --require-encrypted-bundles(orFLEET_REQUIRE_ENCRYPTED_BUNDLES=1) refuses every bundle that is not sealed, for a fleet server you do not trust with plaintext configuration; it is stored in the manifest too, so the server cannot switch it off. See Central management with NSClient Fleet and the security notice.
- ๐ Check your setup
check_filesno longer follows file symbolic links on Windows. The scan already skipped directory reparse points (junctions, mount points, directory links); it now skips file symlinks too, as the Linux scanner always has, so a link planted inside a scanned tree can no longer make the checksum,line_countorversionkeywords read a file outside it. Deduplicated and cloud-backed files are still counted. If you relied oncheck_filescounting file symlinks, point it at the link targets instead. Path allow-list wildcards also changed:*and?in anallowed filesentry no longer cross a directory separator, soC:/logs/*.logcovers the files inC:/logsonly; writeC:/logs/**.logfor the subtree. See Restricting what a check may read.
-
๐ง A new built-in WEB role,
restricted, runs checks without arguments. Nothing to do on an existing install: the role is added to[/settings/WEB/server/roles]but no user is assigned to it, andclient,monitoringand any custom role holdingqueries.executekeep passing arguments exactly as before. The new role holdsqueries.execute.noargsinstead ofqueries.execute, which is the REST equivalent of the NRPE server’sallow arguments = false: the caller may run the checks the agent defines, but a request carrying any query-string parameter is refused with403 Arguments are not allowed for this user. Neither grant implies the other, so the role can never widen into the full privilege;full(*) still confers both.[/settings/WEB/server/users/monitor] role = restricted [/settings/check helpers/alias] check_root_disk = check_drivesize drive=/ warning=free<10% critical=free<5%Give a restricted caller the checks that need arguments as aliases, as above, so the arguments live in your configuration. Note that every query parameter counts as an argument, including a session token passed the legacy way as
?TOKEN=, so such a client must authenticate with a header.
- ๐ง Fleet:
nscp enroll --unenrollleaves the fleet. Nothing to do. Until now leaving meant deleting the enrollment manifest, the fleet directory and the[/includes] fleetentry by hand;nscp enroll --unenrolldoes all three and reports each, then a service restart stops the sync. It is a local act: the host stays listed on the fleet server until you remove it there. See Central management with NSClient Fleet.
- ๐ง Check your setup
check_logfile files=now does what it says. The comma-separated form offile=was parsed before the check read its arguments, so it has been ignored for as long as anyone is likely to have tried it: a check passing onlyfiles=failed with Need to specify at least one file, and one passing bothfile=andfiles=silently read only thefile=entries. Both now work, and the two are one list rather than one overriding the other. If a check of yours passes both, it will read the union from this release on - and every name goes throughfile accessin[/settings/logfile]exactly like afile=one. Nothing to do unless you were relying on the entries being dropped.
- ๐
Check your setup
check_installed_softwaretakes dpkg install dates fromdpkg-query. Nothing to do on a current Debian or Ubuntu host: the date is the same as before, now read fromdpkg-query’sdb-fsys:Last-Modifiedfield instead of from dpkg’s internal database. That field needs dpkg 1.19.3 or later; with an older dpkginstall_dateis now left unset, so an expression on it such aswarning=install_date > -7dno longer matches anything there. The check also returns UNKNOWN when dpkg cannot read a package’s file list for a reason other than the file being missing, where it used to leave only that package undated.
0.20.0¶
- ๐ Action required Generated TLS private keys are now created readable only by the agent, and a
generated CA no longer ships its private key to clients. Certificates
NSClient++ generates itself โ including the one a default NRPE start creates
when
certificatepoints at a missing file โ used to land at0644with an unencrypted key inside; they are now0600(restricted DACL on Windows), and a generated CA keeps its key inca-key.pembeside the distributableca.pem. Existing files are not touched: runchmod 600 /etc/nscp/security/certificate.pem, and if you distributed a generatedca.pem, regenerate that CA and re-issue client certificates โ anyone holding the old file can mint certificates that passverify mode = peer-cert. See the security notice.
- ๐ Check your setup The client host-override guard now also covers a credential kept inside the
target’s address. Two ways past the 0.19.0 guard are closed (see the
security notice):
a secret carried in the URL โ
address = https://h/submit.php?token=SECRETorhttps://user:password@h/โ now counts as the configured credential it is, and a target that supplies a credential but no address no longer adopts the caller’shost=as its own. Nothing to do unless you relied on one of those to point a credentialed target at several hosts; the remedies are unchanged โ pass the credential with the request, configure each destination as its own target and select it withtarget=, or setallow host override = true.
-
๐ Check your setup Undefined-behaviour audit: some inputs that used to misbehave are now rejected. From the security notice; nothing to do unless you relied on one of these:
check_cpu time=0is an error โ the window must be at least one second.- A threshold or unit suffix that overflows 64 bits (
used > 1.0e30T,time=5000000w) is reported as an error instead of silently wrapping. - A module whose load fails is dropped from the plugin list instead of
lingering half-loaded, and no longer answers
exec. NSClientServer(check_nt) andCheckMKServerrestart their listener on a settings reload, so a changed port or password takes effect as it already did forNRPEServer. Connections open at that moment are dropped.- A Python script can no longer unload the
PythonScriptmodule it runs in.
- ๐ โฑ๏ธ Inbound TLS handshakes are now bounded by the listener’s
timeout. The connection deadline was armed only after the handshake had completed, so the handshake phase itself was unbounded: a host permitted byallowed hostscould open sockets, send nothing, and pin one connection object, file descriptor and buffer each, indefinitely. The plain-TCP path was bounded bytimeout(30 s by default) all along; the SSL path โ the NRPE default โ was not. A client on a slow or lossy link that cannot complete a handshake withintimeoutis now dropped instead of lingering; raisetimeouton the listener if that is too tight for your network. See the security notice.
- ๐ Check your setup The NRPE client now says so when it is not authenticating the server, and
generated certificates carry a usable SAN.
verify modedefaults tonone, sossl = truealone gives encryption with no peer authentication and undetectable on-path impersonation. The default is unchanged, but the client now logs one error line per target at its first check; setverify mode = peer-certwithcapointing at the issuer to silence it and actually authenticate the server. Generated certificates now name the machine itself rather than onlylocalhost, so verification is possible at all โ regenerate an existing one to use it. See the security notice.
- ๐ Check your setup The 512-bit Diffie-Hellman parameter file is no longer shipped.
security/nrpe_dh_512.pemwas installed alongsidenrpe_dh_2048.pemand was the default of an unused settings helper. Nothing in the agent used it โ the NRPE server has defaulted tonrpe_dh_2048.pemโ but 512-bit DH is Logjam-broken, and an operator copying the shipped default intodhinherited it. The file and the dead default are gone. If you setdhtonrpe_dh_512.pemexplicitly, change it to${nrpe-dh}/nrpe_dh_2048.pembefore upgrading; otherwise the listener will fail to start with a missing DH file. An existing copy on disk is left where it is by the upgrade. See the security notice.
- ๐ .NET plugins are back, on Windows and Linux. The
DotnetPluginsmodule is built again, now hosting an installed .NET runtime (8.0 or newer) throughhostfxrinstead of the old Windows-only C++/CLI build, so it also works on Linux. Nothing to do on a default install: the module is not loaded unless you addDotnetPlugins = enabledto[/modules], and no runtime is bundled. The Windows installer regained the “.NET plugin support” feature (selected by default, installsmodules/DotnetPlugins.dlland the managed API inmodules/dotnet/); the Linux packages ship the same files when they were built with the dotnet SDK. Plugins are configured under[/settings/dotnet/plugins](<alias> = <assembly>), see the DotnetPlugins reference and Extending with .NET. Plugins written against the pre-0.6 C++/CLI API (NSCP.Core.dllfor the .NET Framework) must be rebuilt against the newnet8.0NSCP.Core.dll; the interfaces are the same.
-
๐ค The WEB server can now cache passive results and serve them over REST. Nothing to do: the feature is off by default and an existing install is unchanged. Set
enabled = trueunder[/settings/WEB/server/results]and restart the service โenabledandchannelare read when the web server starts, because a submission channel cannot be registered by a settings reload โ andWEBServerregisters a submission channel (WEBby default), keeping whatever is submitted to it in memory so a monitoring system that cannot reach the agent can poll the results back out of it. While it is off no channel is registered and the endpoints answer503.Only one result is kept per key (
${host}/${alias-or-command}by default);modepicks which of two results for a key survives โlast(the default, the newest wins) orworst(the most severe wins, so a problem that recovered before the next poll is still reported).GET /api/v2/resultsdrains what it returns unlessclear on poll = false, which is what makesworstmean “worst since the last poll”.Four endpoints expose the cache โ
GET /api/v2/results,GET /api/v2/results/{key},DELETE /api/v2/resultsandDELETE /api/v2/results/{key}โ behind the newresults.list,results.getandresults.deleteprivileges. Those privileges are not part of any bundled role exceptfull, so grant them explicitly to whoever polls:nscp web add-role --role poller --grant results.list,results.get,login.getThe cache is bounded (
max entries, default 1000 keys;max age, default no expiry). See the REST API results page for the full contract.The
check_nsclientbinary bundled with the packages (Windows MSI and the Linux DEB/RPM alike) moves from 1.0.1 to 1.1.0, which is the version that gained theresultscommands โresults list,results show,results delete,results clearandresults feed, the last of which polls an agent’s cache and submits everything in it to Nagios as passive check results. Nothing else about the plugin changes and no existing invocation is affected.
- ๐ง
tls versionnow accepts every spelling it documents.tlsv1.0+,tls1.0+,1.0+,tlsv1.3+,tls1.3+,1.3+,sslv3+,ssl3+andanywere rejected with “Invalid tls version” โ for an NRPE listener that surfaced as “listener failed to start”. Four separate copies of the table of accepted spellings had drifted apart: the maximum-version lookup never stripped the trailing+the way the minimum-version lookup does and listed only some of the+forms literally, and the floor lookup the listener validates through was missingsslv3entirely. They now share one table. Nothing to do; the defaulttlsv1.2+was never affected.
0.19.0¶
- ๐ A
check_servicefilter that matched no service no longer kills the agent. On Windows,check_service "filter=name = 'nosuchservice'"terminated the wholenscpprocess instead of returning a result;check_logfile’scolumn()keyword could be driven into the same fault. Both now return the documented empty-result contract (UNKNOWN: No services found). Nothing to configure โ if you worked around it withservice=<exact name>and nofilter=, service name patterns are usable again. See the security notice.
- ๐ Check your setup A client target no longer lets a request send its configured credentials to
a destination the request names.
host=,port=andaddress=used to move the destination while the target’spasswordortokencame along, so anyone able to run a client module’ssubmit_*/check_*command โ a REST user in the seededmonitoringrole, or an NRPE peer withallow arguments = trueโ could have the agent send that credential to a host of their choosing. That combination is now refused. A target with no credential, a request supplying its ownpassword=/token=, and a request that does not move the destination are unaffected. If you relied on one credentialed target plushost=to reach several servers: pass the credential with the request, configure each server as its own target and select it withtarget=(which now works for queries too), or setallow host override = trueon the target. See the security notice.
- ๐ REST script uploads are staged in a private, randomly named file.
PUT /api/v2/scripts/โฆno longer writes the upload to${temp}/<name>, where a local user could plant a file of the same name and have it imported as a command. No configuration change; a staging failure is now reported as an HTTP 500 instead of importing whatever was on disk. See the security notice.
- ๐ Check your setup Modern layout (opt-in, experimental): the shared folder must be a real
directory. A
%ProgramData%\NSClient++that is a junction or symbolic link is refused by the installer, bynscp settings --migrate-layout modern, and at service start (which fails rather than loading a configuration from behind a link). Relocate the folder with a[paths]override inboot.iniinstead. Legacy (default) installs are unaffected. See the security notice.
- ๐ ๐ฅ Check your setup A multicast collectd target now sends through one interface, not all of
them. A
CollectdClienttarget with no address goes to collectd’s default multicast group (239.192.74.66:25826), and every such target used to send a copy of the metrics through every local interface of the matching address family โ putting them on segments the operator never meant to reach. The new per-targetmulticast interfacesetting decides this:auto(the default) sends one copy through the interface the routing table picks,allrestores the previous fan-out, and a comma-separated list of local IP addresses sends through exactly those. Unicast targets are unaffected. If you depend on a multicast target reaching several segments, setmulticast interface = allon it. See the security notice.
- ๐ Repeated rapid-fire failed WEB logins from one IP are now blocked for longer
each time. The block that follows
auth rate limit max failuresconsecutive failures used to be a fixedauth rate limit block secondswindow (default 60 s) that let an attacker resume guessing at a steady rate forever. A further block now doubles the wait, up to an hour, and resets after a successful authentication or an hour of quiet. Only failures arriving at machine speed escalate: a client retrying a stale password on a schedule keeps the base delay, so it cannot lock out everyone sharing its address behind NAT or a proxy. Nothing to do on a default install.auth rate limit max failures = 0still disables the limiter outright. See the security notice.
- ๐ An NRDP submission over an unverified
httpslink now says so in the log. When a target’sverify moderesolves to no peer verification, the module logs a message naming the endpoint โ the token then goes to whichever server answers โ matching what the Icinga client already does. The connection itself is unchanged. Theverify modehelp text was corrected at the same time: it listed three values the client-side parser rejects (client-once,workarounds,single) and recommendednonefor self-signed certificates; usepeer-certwithcapointing at the certificate instead. See the security notice.
-
๐ง Check your setup
nscp testnow has a real prompt, and writes a command history file. On a terminal the prompt gained line editing, persistent history, tab completion against the command registry, syntax highlighting that shows an unknown query or module name in red before you press enter, and log messages that redraw around what you are typing instead of landing in the middle of it. See Test mode. Two things to know:- History is written to a per-user file โ
%APPDATA%\NSClient++\console-history.txton Windows,$XDG_STATE_HOME/nscp/console-historyor~/.nscp_historyelsewhere (created mode0600on POSIX). Commands typed at the prompt can carry credentials, so if you would rather keep nothing on disk sethistory size = 0under[/settings/cli]. That section also holdshistory fileandcolor. - With stdin not a terminal nothing changes โ no prompt, no history, no colour, and the core keeps writing the log itself. Piping commands in does now work on Windows, where the readiness check used a console-only API and silently ignored a file or pipe; an exhausted stdin no longer spins at 100% CPU on POSIX; and end of input is no longer treated as a reason to exit, which is how the agent is normally started under a supervisor.
Completion after
load/enableoffers the modules that are not already loaded or enabled, andunload/disablethe ones that are. The first such completion in a session pauses while the agent reads the module directory, once per process. - History is written to a per-user file โ
-
๐ง The console log is no longer held back in a buffer. Nothing to do. The console log backend installs a 64 KB buffer on standard output and nothing emptied it, so on Windows log output sat there until something else happened to flush the stream โ in
nscp testthe only thing that did was reading the next line of input, which is why the log appeared to catch up only when you pressed a key. Every message is flushed as it is written now.The same fix means a redirected console log streams instead of accumulating:
nscp test > log.txt, a container, or a supervisor capturing standard output now see each line as it is logged rather than nothing until 64 KB has built up or the process exits.
- ๐ง
--no-stderrand the oneline log format now take effect. Nothing to do unless you were working around the old behaviour. Both were passed to the log level parser, which does not know them, so they were rejected withInvalid log level: no-std-errin the log and never applied. They reach the log driver now.
- ๐ง
target=now selects a configured target on the query path as well. The shared client machinery only ever appliedtarget=/-twhen a command was run as an exec; as a query โ which is what a REST or NRPE caller gets forcheck_*andsubmit_*โ the argument was accepted and then ignored, so the call silently went to thedefaulttarget. It is applied on both paths now, with any explicit option (host=,timeout=, โฆ) still winning over what the selected target says. A query that passedtarget=and relied on reachingdefaultanyway will now reach the target it named.
- โฑ๏ธ Check your setup collectd submissions now honour
timeoutandretries, and report failed sends. Both target settings were read into the connection but never used on the UDP path, and the send discarded its result entirely โ an unreachable target, a full socket buffer or an oversized datagram looked exactly like a successful send, so metrics that never arrived left nothing in the log. A failed send is now retried up toretriestimes (default 3, 20 ms apart) and logged once per distinct failure; the whole send โ name resolution included โ is bounded bytimeout(default 30 seconds), after which the remaining datagrams are abandoned with a message. Retries apply to sends that failed locally, so no datagram the receiver already has is ever sent twice. Nothing to do unless you set a largeretrieson a collectd target โ it now costs real time, bounded bytimeout.
- ๐ง Check your setup An Icinga target address with a path prefix is now honoured. The path of a
target
address(https://proxy.example.com/icinga/) was parsed and then dropped: every call went to/v1/...on the host, so an Icinga 2 master published under a reverse-proxy subpath could not be reached and requests landed on a path the operator did not intend. The prefix is now prepended to every API path (/icinga/v1/actions/process-check-result). Addresses without a path โ the normalhttps://icinga.example.com:5665form, with or without a trailing/โ are unaffected. If a target address carries a path that is not a subpath of the API (a leftover from another client’s configuration, say), remove it: it is now sent.
0.18.1¶
- ๐ Check your setup Icinga API submissions now honour the configured
timeout, and credentials no longer reach the trace log. TheIcingaClientmodule’s HTTP calls previously waited forever โ the target’stimeoutsetting (default 30 s) was read but never applied โ so a stalled Icinga endpoint could silently wedge passive-result submission; settimeout = 0on the target if you depend on the old unbounded wait. The same pass maskedpassword/tokenvalues in the trace-level target dump (this also covers the other client modules sharing that machinery) and added a log message when anhttpssubmission runs with certificate verification disabled (verify modeempty ornone). See Security notices.
- ๐ Filter expressions are now bounded in length and nesting depth. A
filter/warning/criticalexpression โ and a%(...)expression placeholder inside a syntax template โ longer than 1024 characters or nested more than 64 parentheses deep is now rejected at parse time instead of being evaluated, failing with a clear “exceeds the maximum length/depth” error. The where-parser and the expression evaluator both recurse with the shape of the input, so an unbounded or deeply nested expression could exhaust the stack and crash the agent. Real filters are a small fraction of these limits, so the default install and every normal configuration are unaffected โ only a pathologically large or deeply nested expression is refused. See the security notice.
- ๐ Check your setup CheckExternalScripts hardening. A hardening pass over the external
scripts module tightened several rough edges: the
ext-scr installargument lockdown now writes to the setting the module actually reads (previously it was a no-op, so a lockdown could silently not apply), the command timeout is enforced on every execution path with output capped, theshow/deletesandbox resolves symlinks, and%/^are blocked on the shell-fallback path. The default install is unaffected (arguments are off by default). If you rely onext-scr installto disable arguments, re-run it after upgrading so the effective setting is written. See the security notice.
- ๐ Check your setup NSCA-NG cert mode now actually verifies the server (and presents the
client certificate). In 0.18.0 the
NSCANgClientcert mode (use psk = false) applied its TLS configuration to the OpenSSL context after the connection object had been created from it, and OpenSSL copies the verify mode, client certificate, cipher list and TLS-version bounds out of the context at creation time โ soverify mode = peer-certwas silently ignored, any certificate the server presented was accepted, and the configured client certificate was never sent. The configuration is applied before the connection is created now. Two operator-visible consequences: a cert-mode target that “worked” against a server whose certificate does not chain to the configuredca(or does not match the host name) will now fail to connect โ that is the verification working; fix the server certificate, or opt out explicitly withinsecure = trueif you accept the MITM risk. And servers that require a client certificate will start seeing it. The default PSK mode (use psk = true) is unaffected. See Security notices.
- ๐ Check your setup NSCA: an unrecognized
encryptionvalue is now a hard error instead of silently running without encryption. A typo’d algorithm name (aes-256), or one not compiled into the build, used to fall back to no encryption on the end carrying it. Since the ciphers must match, a one-sided typo showed up as the peer rejecting every submission with a CRC error rather than as accepted plaintext โ but that failure gave no hint of its cause, and a value broken the same way on both ends did run plaintext while looking encrypted. Now theNSCAServermodule refuses to load and anNSCAClientsubmission fails, each naming the problem and listing the available algorithms. Default installs (aes256) are unaffected. Breaking only for setups relying on the fallback: fix the algorithm name, or setencryption = noneexplicitly if plaintext was intended โ this includes builds compiled without crypto++, where any cipher name previously degraded to plaintext and the server now refuses to start. See the security notice.
- ๐ Check your setup NSCA hardening: empty-password warning,
performance data = falsehonoured, wire-field validation. Enabling NSCA encryption with an emptypasswordnow logs an error on both ends (the password is the key, so an empty one is a well-known key) โ set the same password on both ends to clear it.NSCAServer’sperformance data = falsenow actually strips perfdata from forwarded submissions (it was silently ignored). Inbound host/service names are stripped of control characters and out-of-range status codes are clamped to UNKNOWN. No action needed on a default install. See the security notice.
- ๐ Check your setup NRDP submissions now honour
timeoutandretry. Both settings were parsed but never applied: a submission to a server that accepted the connection and then stalled hung the submission thread forever, and failed submissions were never retried. Every step of the exchange (connect, TLS handshake, proxy tunnel, request, response) now runs under the configuredtimeout(default 30 seconds for configured targets, 10 for one-shotnscp clientsubmissions), and transport failures are retried up toretrytimes. Nothing to do unless your NRDP endpoint legitimately takes longer than the timeout to answer โ raisetimeouton that target. The response body is also capped at 5 MB, far above any real NRDP reply. See the security notice.
- ๐ A
tls versionwith a trailing+now means “that version or later”.1.2+(the common default) previously negotiated TLS 1.2 only; it now also permits TLS 1.3, andanyis accepted as the documentation always claimed. This applies everywhere the setting exists: NRDP and the other HTTP-based clients, the NRPE/NSCA clients and servers, andcheck_tcp. No action needed; pin an exact version (tls version = 1.2) if a peer misbehaves when TLS 1.3 is offered. See the security notice.
- ๐ Check your setup The Elastic module now verifies HTTPS server certificates and can
authenticate.
ElasticClientpreviously hardcoded TLS verification off; anhttps://address now defaults toverify mode = peeragainst the platform CA bundle, with newtls version,verify modeandcasettings to tune it. Newuser/passwordandapi keysettings authenticate against secured clusters (Elasticsearch 8+ defaults), and a newtimeout(default 30s) bounds each submission. If you rely on a self-signed certificate, pointcaat it or setverify mode = noneexplicitly. See Security notices.
- ๐ค Check your setup The Elastic module no longer sends the legacy
_typeparameter by default. Mapping types were removed in Elasticsearch 8, which rejects bulk requests carrying them, so theevent type,metrics typeandnsclient log typedefaults are now empty. Only Elasticsearch 6.x or older needs them: set the old values (eventlog,metrics,nsclient log) explicitly to keep the previous behaviour. Batched documents also now get distinct ids โ previously all documents in one bulk request shared an id and overwrote each other, so multi-entry events show up completely now.
- ๐ Check your setup check_mk client targets: configured TLS settings are honoured again.
check_mk_target_object::read()added the SSL keys (use ssl,certificate,verify mode,ca, โฆ) to its settings registry but never calledregister_all()/notify(), so values set on a[/settings/check_mk/client/targets/โฆ]section were silently ignored and the client connected in plaintext regardless of configuration. The keys are read (and documented) again. If you configureduse ssl = trueon a check_mk target, the connection becomes TLS on upgrade โ make sure the server side actually speaks TLS, or the check starts failing. Details in the security notice.
- ๐จ Check your setup Syslog client targets: configured severities and templates are honoured
again. The same defect existed in the syslog client’s target object: the
severity,facility,tag_syntax,message_syntaxand per-status severity keys on a[/settings/syslog/client/targets/โฆ]section were never read, so the built-in defaults (error/kernel/โฆ) always won. Values you configured โ perhaps years ago, without effect โ now apply; if your syslog routing depends on the previously effective defaults, review the target sections for stale keys.
-
๐จ ๐ Check your setup Syslog messages now carry the RFC 3164 HOSTNAME field, and several syslog options work for the first time (see the security notice). What changes on the wire and in behaviour:
- Datagrams now read
<PRI>TIMESTAMP HOSTNAME TAG MESSAGE. Thehostnamesetting under[/settings/syslog/client]โ until now read but never used โ fills the HOSTNAME field (defaultauto, the machine name). Receivers that promoted the tag (defaultNSCA) to origin host will now file records under the real host name; adjust any log-parsing rule that keyed on the old, hostname-less format. - The
tag_syntaxandmessage_syntaxtarget settings now reach the wire. They were stored under keys the sender never read, so a settings-defined target sent an empty tag and dropped the message text. - The per-state severity options (
ok-severity,warning-severity,critical-severity,unknown-severity) passed as command arguments now take effect; they too were stored under keys that were never read. - An unknown
severityorfacilityname now degrades to priority<13>(user.notice) instead of<0>โ which is kernel.emergency, a priority many receivers page or broadcast on. A missing per-state severity now falls back to the baseseverityinstead of tripping that fallback. - 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.
- Datagrams now read
- โฑ๏ธ Check your setup The Graphite
timeoutis now enforced โ as a budget for the whole submission. It was doubly dead before: the value the operator configured never reached the connection (the lookup read a map the well-knowntimeoutkey is not stored in, so the default 30 always won), and the connection did not use even that โ resolution, connect, the TLS handshake and every write ran with no deadline, so a stalled carbon endpoint could hold the submitting thread for the OS-level TCP timeout, or indefinitely on a stuck write. The configuredtimeout(default 30s) is read now and bounds the whole submission as one budget, like the SMTP client’s. A target whose submissions previously completed by quietly taking longer will now fail at the configured value; raisetimeouton targets talking to a slow relay. See Security notices.
- ๐งน The Graphite
retrysetting is not honoured and is no longer read. The module always made exactly one attempt per submission; reading the value made it look otherwise, and a retry loop would multiply the worst-case time a stalled endpoint can hold the submitting thread by the retry count.retry/retriesare registered for every client module centrally, so they still appear in the GraphiteClient reference, butGraphiteClientdoes not act on them. Mirrors the SMTPretrychange in 0.18.0.
0.18.0¶
-
๐ฅ Check your setup
check_nscpis now a filter check, and it can see crash reports again. Crash reporting itself has always worked โ the agent has archived crash dumps since 0.4.x โ butcheck_nscp’s count of them has not, for two independent reasons that accumulated over the years:- The count matched files whose extension equalled
txt, while the helper it used returns the extension with its leading dot (.txt). That comparison has been false since the check was written in 0.4.2, so the crash count read 0 whatever was in the folder. - 0.6.10 dropped Google Breakpad, whose vendored submodule and build
machinery had become a dependency burden, along with the separate
crash-report sender tool that shipped with it. The handler that replaced
it writes one plain-text
<timestamp>.crashfile per crash โ the exception, the faulting address and the module it landed in โ where breakpad left a<guid>.dmpminidump plus a<guid>.dmp.txtdescription. From 0.6.10 on, even a corrected.txtmatch would have found nothing.
Separately, 0.4.3 stopped the module reading the archive folder from
[/settings/crash]archive folderand hardcoded the compile-time default instead, so on any installation that had moved the archive folder the check was looking in the wrong place as well.All of that is fixed. Crash reports are recognised by
.crash, and still by.dmpand.txtso pre-0.6.10 archives keep counting; the configuredarchive folderis read again; andcheck_nscpnow exposescrashes,last_crash,crash_age,errors,last_error,uptime,versionanddateas filter keywords, accepts the usualfilter/warning/criticalandtop-syntax/detail-syntaxoptions, and emits perfdata for whichever keywords the thresholds name.The default verdict is unchanged โ any crash report or any logged error is CRITICAL โ but two things change for existing users. The message is now
N crash(es), M error(s), uptime <duration>without the appendedlast crash:/last error:fragments. To put them back, pass adetail-syntax:check_nscp "detail-syntax=${crashes} crash(es) (${last_crash}), ${errors} error(s) (${last_error}), uptime ${uptime}"And because the crash count now works, an agent with an old report still sitting in the crash archive folder will start reporting CRITICAL where it previously read 0 โ threshold on
crash_age(for example"crit=crash_age < 7d") if you only care about recent crashes, or clean the folder out. Crash reports remain a Windows-only concept; on Linux there is no crash handler, socrashesis always 0. - The count matched files whose extension equalled
-
๐ค Check your setup
check_and_forwardnow actually submits the result. The command ran the wrapped check, answeredMessage submittedand delivered nothing: the submission was built in a form the channels could not read, so NSCA, NRDP, Graphite and every other client module received a message with no results in it. It now submits like a scheduled check does. If you had given up on the command, it works โ and if you built a workaround around its silence (for instance a schedule that exists only to be triggered), that workaround is no longer needed. The command also gainedchannel(targetstill works as a synonym),alias,destinationandsourceoptions, and names the result after the command when no alias is given.Breaking: the command no longer accepts positional arguments for the wrapped command.
check_and_forward command=check_cpu warn=load>80used to (try to) hand the bare trailing tokens to the wrapped command; it now fails to parse. Pass each wrapped-command argument througharguments=instead, one per argument:check_and_forward command=check_cpu "arguments=warn=load>80" "arguments=crit=load>90"The positional form had to go because its parser also swallowed the CLI’s own
--argument key=valuetokens, which is what fed the wrapped command garbage and made it fail. If a submission is rejected by the channel the command now reports that failure instead ofMessage submittedโ including when only one channel of a comma list (channel=NSCA,GRAPHITE) fails, which previously was silently reported as success.
- โ๏ธ A reload now loads modules enabled in an included file since the last one.
An
[/includes]file is read once when the configuration is loaded and served from memory after that, so a module switched on in one โ most importantlyfleet.ini, which the fleet sync rewrites whenever a bundle changes โ was not actually loaded until the service next restarted. Nothing said so: the file on disk was right and a fleet host reported itself in sync, while the module quietly did nothing. The visible case was a fleet bundle turning onNRDPClientandSchedulerto start passive submissions, which then never arrived. A reload now re-reads the included files first, so settings changed in them take effect and newly enabled modules are loaded; modules already running are left alone. Two things are unchanged: a module disabled since the last load keeps running until the service restarts, and[/modules]in the mainnsclient.iniis still only read at startup.
- ๐
New: run scheduled checks on demand with
run_schedules. After editingnsclient.iniyou no longer have to wait out the interval to see the new result on the monitoring server โnscp client --boot --query run_schedules(optionally--argument schedule=<alias>) runs the configured schedules now and submits their results on their normal channel. It is a regular check command, so it also works over NRPE, REST and innscp test. Nothing changes for existing configurations; the timers are untouched. See Passive monitoring โ Step 6.
- ๐ซ A denied check is no longer submitted as a passive result. The permission
layer answers a denied query as a successful query carrying an UNKNOWN
“Permission denied” payload, and both
run_schedulesandcheck_and_forwardforwarded that to the monitoring server โ overwriting the last real result for that service while telling the caller it had succeeded. Both now refuse withPermission denied: not allowed to run <command>, nothing was submittedand touch no channel. If you restrict what a REST or NRPE identity may run, expect the denial as an error where you previously saw a stale UNKNOWN appear on the server. See Permissions.
- ๐ง Check your setup
settings --update --add-defaults --use-samplesnow writes the sample objects. The flag was parsed and never read, so it behaved exactly like the plain invocation. It now writes the registered/samplesections, and--remove-defaultsstrips them again โ the two are now exact inverses. An edited sample is still never overwritten or removed, and without the flag the output is byte-for-byte what it was. If you have scripted--add-defaults --use-samplesexpecting today’s (sample-free) output, drop the flag.
- ๐ฌ Check your setup
nscp client --query <cmd>no longer appends “No module was specifiedโฆ”. The line was appended to every result when no--modulewas named. Scripts that stripped or matched it can stop; naming a module is unchanged.
- ๐ง Settings writes work again when
[/includes]names a directory. Saving handed the directory path to the INI writer, and the resulting “Is a directory” error aborted the whole save โ sonscp settings --setand the web UI failed and the main file was never written. Directory includes are read-only by nature and are now skipped when saving. If you worked around this by removing a directory include, you can put it back.
- ๐ Check your setup NRDP HTTPS submissions now verify the server certificate by default on
the
nscp client/REST path. Previously anhttps://submission made that way with noverify modeset trusted any certificate silently (configured targets already defaulted topeer). If you submit to a self-signed NRDP endpoint that way and want to keep skipping verification, pass--verify none(or point--caat the certificate) explicitly. Plainhttp://is unaffected. Two further NRDP hardening fixes ship in the same release โ a malformed server response can no longer crash the agent, and the NRDP token and any proxy-URL credentials are redacted from the trace log. See Security notices.
- ๐ก Check your setup
--source-host/--sender-hostnow name the sending host, on every client module. They were registered against the destination container, where the well-knownhostkey 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.SMTPClientandNRDPClienthad each worked around this by registering their own copies, which made the option name ambiguous and so unusable on those two modules (option '--source-host' is ambiguous). The options are registered once now, against the sender. If you had scripted around the old behaviour by passing--source-hostto redirect a connection, use--hostor--addressfor that instead.
- ๐ Check your setup SMTP submissions now verify the server certificate against the agent’s
CA bundle.
SMTPClientrelied on OpenSSL’s built-in default verify paths, which on Windows do not include the Windows certificate store โ so the defaultsecurity=starttlsfailed verification against Gmail, Microsoft 365 and every other public provider there, andinsecure-skip-verifywas the only way through. A newcatarget setting (and--caargument) defaults to${ca-path}, the same trusted bundle the other TLS clients use: the distribution’s CA store on unix, the exported Windows ROOT store on Windows. If a Windows SMTP target was only working because you setinsecure-skip-verify = true, remove it and retry โ verification should now succeed. For an internal relay with a private CA, pointcaat that bundle instead of waiving verification. Setca = noneto restore the old behaviour. A bundle that cannot be loaded now fails the submission with a message naming the file, rather than failing the handshake later with an unrelated-looking issuer error. A target that names nocaat all โ a one-shot command line, or a default target โ falls back to the same bundle, resolved once at module load, so no submission path is left on OpenSSL’s built-in verify paths by accident.
- ๐ SMTP client security hardening. The same pass closed a set of
trust gaps in
SMTPClient: data pipelined into the STARTTLS greeting is refused rather than trusted as post-handshake input, the EHLO name is validated for command injection before it reaches the wire, and ESMTP capabilities are matched per reply line instead of by substring search. No configuration change is needed. See Security notices.
- โฑ๏ธ Check your setup The SMTP
timeoutis now a budget for the whole submission rather than a fresh deadline per operation (and per resolved address). A target that previously completed by using several times its configuredtimeoutacross a slow session will now give up at the configured value; raisetimeouton targets talking to a slow relay.
- โ๏ธ SMTP messages now carry a
Message-IDheader. Nothing to do โ it improves deliverability and gives mail administrators a handle to trace a notification by.
- ๐งน The SMTP
retrysetting is not honoured and is no longer read. The module always made exactly one attempt per submission; reading the value made it look otherwise.retry/retriesare registered for every client module centrally, so they still appear in the SMTPClient reference, butSMTPClientdoes not act on them.
- ๐ NRPE hardening: a new
expose versionsetting, and the metachar guard now also checks decoded input. Nothing to do on a default install. Two things are worth knowing.NRPEServergainedexpose version(defaulttrue, which keeps the legacy bannercheck_nrpeexpects); set it tofalseto answer the unauthenticated_NRPE_CHECKping with a generic message instead of the exact build. Andallow nasty characters = falsenow re-checks the decoded command and arguments, not just the raw wire bytes โ with a non-UTF-8encodingset, a multi-byte sequence could previously decode into a metacharacter that was never literally on the wire, so a request the guard was always meant to block may now be rejected. Separately,nscp nrpe installreads the storedverify modeagain instead of silently resetting it on a re-run. See the security notice.
- ๐ Check your setup WEB server security hardening. A review of the
WEBServermodule produced several defense-in-depth fixes (session tokens now come from the OpenSSL CSPRNG, cookie-name matching requires a name boundary, the installer refuses an HTTPSโHTTP redirect, and thelegacygrant’s startup warning now names/settings/query.pb). The default install needs no action. Two changes touch observable behaviour: a script or module name that begins with-is now rejected (rename it; interior dashes are fine), and the legacyPOST /auth/logoutroute now enforcesallowed hostslike the rest of the API (a caller outside the perimeter gets 403). A third only bites on a broken system: if the OpenSSL CSPRNG fails, the server now refuses to issue a session token (HTTP 500, logged asSECURITY:) rather than falling back to a weaker generator. See the security notice.
- ๐ The bundled OpenSSL is updated from 3.5.4 to 3.5.8 in the Windows
builds, picking up the fixes from four upstream security releases on the
3.5 LTS line. The most relevant fix for NSClient++ is
CVE-2025-11187, a stack
buffer overflow parsing a crafted PKCS#12 file โ reachable through
check_certificatewhen it scans certificate files that less-trusted principals can write to โ alongside further PKCS#12/ASN.1 and TLS-stack fixes. No configuration change is needed. The Linux packages link the distribution’s OpenSSL and are unaffected. See Security notices.
0.17.0¶
-
๐ท๏ธ Check your setup Check-specific filter keywords that clashed with the generic summary keywords are renamed. A handful of checks registered their own keyword named
status,countortotalโ the same names as the built-in summary keywords (%(status),%(count), โฆ). The check-specific value won infilter/warning/criticalanddetail-syntax, whiletop-syntaxand the reference documentation showed the generic one, which was confusing. Each such keyword now has a distinct, documented name:Check Old New check_cpu,check_cpu_utilizationtotalusagecheck_batterystatusbattery_statuscheck_networkstatus,totallink_status,throughputcheck_os_updatescountupdatescheck_patch_agecountpatchescheck_pending_rebootcountsignalscheck_printjobsstatusjob_statuscheck_printqueuestatusprinter_statuscheck_installed_software(Linux)statuspackage_statuscheck_activationstatusactivation_statuscheck_dockerstatuscontainer_statuscheck_connectionscount,totalconnections,total_connectionscheck_dnscountrecordscheck_httpstatusstatus_messagecheck_shadowcopycountcopiescheck_disk_healthtotalsizeExisting configurations keep working: the old names remain as undocumented deprecated aliases with unchanged behaviour, so filters like
check_cpu "warn=total > 80"still work. Migrate to the new names at your convenience; only they appear in the reference documentation. A few behaviour changes ride along:check_os_updates’ default output now reports the actual number of updates (rendered from the detail line via${list}) instead of the matched-row count the old default showed.- Three default perfdata keys change because the default
perf-configlists the renamed keyword:check_cpu_utilization(Linux)cpu_totalโcpu_usage,check_patch_agepatch_countโpatch_patches,check_pending_rebootreboot_countโreboot_signals. Pass your ownperf-config=extra(...)with the old (alias) keyword to keep the old key. All other perfdata keys are unchanged (the renames keep their original perf suffixes, andcheck_connections’ default perf-config intentionally still uses the alias for series continuity).
- ๐ Check your setup
${host}and friends now resolve in attachment paths and in[/includes]. Host name placeholders were only expanded in settings urls and in the url an attachment is fetched from, not in the path it is written to nor in an included file name. An unknown${...}token in a path is not an error - it resolves to the installation directory - so a configuration such as[/attachments] ${shared-path}/${host}.ini = ...did not fail, it quietly wrote one file with the installation directory in its name. Those paths now name the host, which changes where such a file lands: check any${host},${hostname}or${domain}you already have under[/attachments]or[/includes], and remove the workaround if you scripted around this. Configurations without a host name placeholder are unaffected. When the substitution lands in a local path, the value is sanitized to the characters a legal host name can contain (see the security notice). Likenscp settings --switch,nscp settings --migrate-to(and the REST migrate) now keeps a placeholder you pass it as-is inboot.iniwhile migrating into the expanded per-host file, so the template survives on a fleet-managed machine.
- ๐ข Check messages can now be told how to render their numbers. Every filter
check gained four options -
decimals,byte-unit,decimal-separatorandthousands-separator- socheck_drivesizecan report141.06GB/1006.85GB(or141,06GB/1.006,85GB) instead of140.293GB/0.983TB. The defaults are unchanged, so an installation that does not set them renders exactly what it rendered before. The options only touch the message: performance data keeps its full precision and its.radix, and so do the numbers you write in a filter or a threshold. Real-time filters take the same settings asdecimals,byte unit,decimal separatorandthousands separatorkeys, inheritable from the default template.decimalsis capped at 15 (adoublecarries no more than that): the query option and theformat_bytes()/format_number()argument reject a larger value, and the settings key clamps it, so a typo likedecimals=1000000can no longer make a check try to render a multi-megabyte number. Note that setting any of the four options also moves plain float keywords in the message onto the number format: withdecimalsunset they then render with up to three decimals (trailing zeros stripped) instead of the legacy 6-significant-digit form โ2.71094becomes2.711, and large values stop rendering scientific (1.23457e+07). A pipeline that matches float text in the message may need its pattern relaxed when you first set one of these options; leave all four unset and the message is byte-for-byte unchanged.
- ๐ข Check your setup An unknown unit in
format_bytes()is now reported instead of rendering nonsense.format_bytes(used, 'gb')used to render1.27055e-10because the unit comparison was case sensitive, and any misspelled unit renderedvalue/1024^7. Lowercase units now work, and a unit that names nothing (say'ZB') makes the check reportFilter processing failed: format_bytes failed: Unknown byte unit: ZB. A syntax string with such a typo returns UNKNOWN rather than a quietly wrong number - fix the unit, or the check will stay UNKNOWN.
- ๐ Check your setup A
unit:inperf-configthat names no unit no longer divides the metric by 1024โท. An unrecognised unit now leaves the value alone. If a graph of yours has been flat at a near-zero value, check theunit:spelling in itsperf-config: the metric will jump to its real magnitude on upgrade.
- ๐ Check your setup
perf-config’sunit:now converts plain byte series instead of relabelling them. On series that are byte counts but do not auto-scale (most byte keywords outsidecheck_drivesize),unit:KBused to change the label only, shipping=1536KBfor a value of 1536 bytes - a metric off by the unit ratio to any consumer that trusts the label. The value and the warn/crit bounds now convert into the requested unit, matching what the auto-scaling series always did. A dashboard that compensated for the mislabelling will see the metric drop by that ratio on upgrade. Series not measured in bytes (ms,%,s, …) and explicitminimum:/maximum:overrides are unaffected, and aunit:that names no byte unit still only changes the label.
- ๐งฉ Check your setup Errors raised while a template renders are now reported. A function that
failed inside
detail-syntaxortop-syntaxused to leave the placeholder empty and say nothing; the check now returns UNKNOWN withFilter processing failed: โฆ. This surfaces template mistakes that have been silently producing incomplete messages.
- ๐ช Check your setup
check_pending_reboot’s default message now names the pending-since time. When the reboot was queued by Component Based Servicing or Windows Update, the message gains a suffix:Reboot required: Windows UpdatebecameReboot required: Windows Update (pending since 2026-08-16 09:41:12). Notification pipelines that match the exact message text (an anchored regex, a string equality) need their pattern relaxed; thresholds, states and existing keywords are unchanged.
- ๐ข Check your setup Filter comparisons between a text keyword and a bare number are now
numeric. A string-typed keyword compared against an unquoted number used
to order lexically โ
filter=value > 90onfilter_perfmatchedvalue=100as false (“100” sorts before “90”) โ or, with the operands reversed (90 > value), failed to evaluate at all. Both now compare as numbers, whichever side the keyword is on: the row’s text is parsed per record, and a value that is not a number simply never matches (the check logs one warning naming the value; the result stays a certain non-match, not UNKNOWN). This applies to keywords such asvalue/warn/crit/min/max(filter_perf,render_perf),speed(check_network),string_value(check_registry_value) and thecolumn()function (check_logfile). Quoted literals keep the lexical comparison โversion < '8'still orders as text โ as dolike,regexpandin, keyword-specific converters (state = 'running',age > 30m), and the= 'unknown'/= 'never'sentinels for optional values. Review any filter that deliberately relied on text ordering against a bare number: quote the number to keep the old behaviour.
- ๐ข Check your setup Fractional numbers in thresholds are no longer truncated or rounded.
count > 2.5used to evaluate ascount > 3(the literal was rounded into the counter’s integer domain); unit literals lost their fraction entirely, soworking_set > 1.5gmeant 1g anduptime < 2.5hmeant 2h. Fractions now mean what they say. Whole-number thresholds are unchanged; only expressions that already used a decimal point can behave differently.
- ๐ Check your setup
filter_perf/render_perf/xform_perf: themaxandminfilter keywords were swapped.maxread the perf-data minimum bound andminthe maximum. They now read the bounds they name โ a filter that compensated for the swap needs the two names exchanged back.
- ๐จ Check your setup Syslog submission works again, so a configured syslog server will start
receiving traffic.
SyslogClientread its connection settings from the wrong place, so the target’s address, port, facility, severity and templates were all ignored: the agent loggedUndefined facility:and sent nothing. Broken since 0.4.3 (2015). 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.CheckMKClienthad the same defect on its query path.
- โ๏ธ Check your setup SMTP notifications now announce this host in EHLO instead of
localhost. The sender’s host name was read from the wrong place, so it was always empty and the EHLO fell back tolocalhost. If your mail server applies HELO/EHLO policy (SPF checks, or a rule that rejectslocalhost), the agent will now identify itself properly - setehlo-hostnameon the target if you need a specific name.
- ๐ง Check your setup
nscp settings --shownow says so when--keyis missing.--show --path /some/pathwithout a--keyused to print nothing and exit 0; it now reportsInvalid command line please use --path and --key with showand exits non-zero. A bare--showstill describes the active settings store, and--show --path โฆ --key โฆis unchanged. Scripts that relied on the silent success need the missing--keyadded.
- ๐ฌ Client commands shorter than eight characters work again. A command
such as
cpuorrunansweredException processing command line: basic_string::substr โฆinstead of running, in every module built on the shared client machinery (NRPE, NSCA, NRDP, Graphite, โฆ). Remove any workaround that renamed such commands to a longer alias; no configuration change is needed.
- ๐ง The settings diff no longer reports changes that were already saved.
get_changes()โ behind the REST settingsdiffendpoint and any operator-facing “what am I about to save?” view โ kept listing an edit for the lifetime of the process after it had been written, reporting it as amodifiedentry whose old value equalled its new one. Tooling that treated a non-empty diff as “unsaved work pending” no longer needs to special-case that; no configuration change is needed.
0.16.4¶
- ๐ The bundled Mongoose web server is upgraded to 7.23, fixing two
critical (CVSS 9.1) HTTP request-smuggling vulnerabilities in its HTTP
parser (CVE-2026-73256,
CVE-2026-73257). This
affects the Windows builds of the
WEBServermodule (REST API / web UI) and is exploitable when NSClient++ sits behind a reverse proxy or WAF โ upgrade promptly in that topology. The Linux packages use the Boost.Beast backend and are unaffected. No configuration change is needed. See Security notices.
0.16.3¶
- ๐
check_nt(NSClientServer) answers the real nagios-plugins client again. Requests without a trailing newline used to hang until the client timed out (No data was received from host!) โ broken since 0.12.2. Remove any client-side timeout/retry workarounds; no configuration change is needed. If you expose this legacy endpoint, see the new guidance on securing it (password,allowed hostsand theallowcommand list).
0.16.2¶
- ๐ Check your setup Sensitive settings values are redacted on read. The REST settings
read endpoints (
GET /api/v2/settings/...,/descriptions) andnscp settings --list/--shownow return***for keys registered sensitive, matching thediffendpoint. No action required; tooling that read such a value back now receives***. See Security notices.
- ๐ The built-in
legacyWEB role is no longer seeded on fresh installs, and any role granting thelegacypermission now triggers aSECURITYwarning at startup (and fromnscp web add-role/add-user). Existing installs are unaffected โ the role stays in their config. Thelegacygrant unlocks the deprecated/query.pband/query/{name}query-dispatch endpoints, so a token with it can run any registered check/command; only grant it to trusted legacy systems. See Security notices.
0.16.1¶
- ๐ง RHEL/SUSE: workaround
ca=arguments can be dropped โ${ca-path}now resolves on its own (the explicit form still works). Packagers cross-building for another distribution should set-DCONFIG_CA_PATH=.
- ๐
check_logfileis unchanged unless you opt in tobookmark/max-lines. Adoptingbookmarkis a trade-off: a line is consumed when the check runs (not when its result is submitted) and positions are saved on clean shutdown, so a crash re-reports the backlog. Prefer an explicit bookmark name for a check whose filter changes often.
- ๐ง Check your setup Settings URLs with a query string now send it. A server that relied on receiving the bare path will now see the parameters. The offline-boot cache file is migrated to the query-aware name once on first start.
- ๐ท๏ธ Check your setup
${hostname}in an existing config changes meaning โ it is now expanded everywhereexpand_hostnameis used (including submit clients’hostname), where it used to be left as literal text.
- โฑ๏ธ
run on startupis off by default so the default install is unaffected; if you enable it for thedefaultschedule usestartup windowto avoid a thundering herd.
- ๐ Check your setup Building the HTML docs on non-Windows now needs
-DNSCP_BUILD_DOCS_HTML=ON.
0.14.1¶
- โ๏ธ Licence change: now distributed as Apache-2.0 OR GPL-2.0-only โ a clarification/relicensing with no code or runtime behaviour change. Review it if your organisation tracks bundled-software licences.
- ๐ Check your setup CheckNet perfdata is on by default. If you added perfdata manually, make sure you are not now emitting it twice.
- โ๏ธ Boolean check arguments (
option=true/option=false) now work from the CLI as well as REST; bare-flag usage is unchanged.
- ๐ Action required
CheckSecurityis not loaded by default. Enable it before using its checks (nscp settings --active-module CheckSecurity). Windows-only checks return UNKNOWN elsewhere.
0.12.6¶
- ๐ New permission policy layer, disabled by default. Existing installs
behave exactly as before until an operator sets
/settings/permissions/enabled = true. If you opt in: per-command rules apply to queries only (exec is gated by the separateallow execboolean, which defaultstrue); roll out withlog allows = truefirst to inventory real traffic. See Permissions.
- ๐ NRPEServer
client identity sourcedefaults tonone(previous behaviour). Set tocnonly after configuringverify_mode = peer-certand aca pathpinned to your private monitoring CA โ the system trust store would accept any public cert’s CN.
- ๐ Check your setup
[/paths]overrides from an older install moved to[paths]inboot.ini(same section name, different file). No automatic migration โ copy each entry across and delete the old section.
- ๐ WEB
disable admin user = trueis a new opt-in for status-only WEB exposure; existing installs keep their admin unchanged.
- ๐ Check your setup NRPEServer now survives a failed listener (logs an ERROR, leaves the module loaded) instead of failing the whole module. Add “NRPE listener failed” as a signal if you alerted on module-load failure.
- ๐ Check your setup
insecure = trueon NRPEServer now logs at ERROR (louder, behaviour unchanged) โ whitelist the message on agents intentionally run insecure.
0.12.5¶
- ๐ Check your setup
[/paths]users: copy entries into[paths]inboot.ini; the settings-side section is no longer consulted. Default installs are unaffected.
- ๐งฉ Check your setup Custom-plugin authors: implement the new optional
prepare_shutdowncallback if your module manages sockets or background threads โunloadis now a last-resort teardown.
- ๐ Monitoring-only WEB deployments:
disable admin user = trueunder[/settings/WEB/server]suppresses the built-in admin even on first boot; define your own read-only users (or a tightly scopedanonymousrole).
0.12.4¶
- ๐ Icinga
check_nscp_apiworks again after upgrade with no config change. For a non-stock probe, set[/settings/WEB/server] legacy query auth user agentsto a substring of its User-Agent. For the strict 0.12.3 behaviour (no query-string credentials at all), set that key to empty.
0.12.3¶
- ๐ Action required Audit
allowed hostson every node โ empty values now reject everything.
- ๐ Action required
check_nt(NSClientServer) now defaults tossl = true; setssl = falseexplicitly if your clients don’t speak TLS.
- ๐ Check your setup Replace clients that call
/auth/tokenor/auth/logoutwith the/api/v2/loginflow, and any that pass?TOKEN=/?__TOKEN=in the query string with a header-based token.
- โฑ๏ธ Action required Scheduler cron expressions on non-UTC hosts shift to local time โ update
them or set
[/settings/scheduler] timezone = utc.
- ๐ง Check your setup Review
check_service/check_process/check_filesfilters that relied on the old (now corrected) behaviours.
- ๐ Action required Restart and review the log for new “refused alias” / “rejected connection” warnings โ configurations that were previously silently accepted.
0.11.33¶
- โ
Check your setup No configuration migration required (new
proxykeys are opt-in). Thecheck_filesfixes change a few corner cases:max-depth=0now scans the top directory (#730); missing paths return UNKNOWN (#613); junction loops are not double-counted (#605); empty results return OK instead of UNKNOWN (#717). Review alerting that relied on the old corner-case behaviour.