Releases: mickem/nscp
Release list
0.16.1
Log files you can actually poll, and settings urls that carry their query
0.16.1 is a follow-up release to 0.16.0. It makes check_logfile usable as a polled
check — it can now report only what is new, or only the newest lines — lets http
settings urls carry query parameters and host name placeholders so one boot.ini
can configure a whole fleet, gets TLS working out of the box on RHEL-family and
SUSE, and lets a scheduled check report immediately after a restart.
Highlights
check_logfilebookmarks. Abookmarkoption remembers how far the previous
check read and resumes from there, so a singleERRORline is reported once
instead of failing the check for as long as it stays in the file. Rotation and
truncation are detected by size and content fingerprint. #561check_logfiletailing.max-lines=Nexamines only the newest N lines, and
newest=last|firstsays which end of the file those are at. Without a bookmark the
check seeks to the last N records rather than reading the file, so tailing a
multi-gigabyte log costs a few kilobytes. #583- Settings urls send their query. A query string on an
http(s)://settings url
was silently dropped, so a script generating per-host configuration never saw the
parameters. It is now sent, percent-encoded where the request line requires it, and
each distinct query caches separately. #460 - Host name placeholders in settings urls.
${hostname},${host},${domain}
(and their_lc/_ucvariants) expand anywhere in a settings url, which turns one
boot.iniinto fleet-wide configuration. - TLS works on RHEL and SUSE.
${ca-path}was hardcoded to the Debian bundle on
every non-Windows platform, so on RHEL-family every TLS check — andnscp enroll,
which defaults--cato this token since 0.16.0 — failed before opening a socket.
The path is now detected per platform at build time. run on startupfor scheduled checks. A schedule can run its command once as
soon as the agent is up instead of leaving a stale result behind for a whole
interval after a reboot. #392- Settings urls stay out of the log. A token in a settings url was written to the
log verbatim on every boot, and printed bynscp settings --show. Settings urls are
now rendered as scheme, host and path everywhere. - Scheduler reload fixes. Reloading the Scheduler kept the pre-reload tasks
alongside the new ones, so every schedule ran twice after a reload.
Detailed changes
CheckLogFile — report only what is new (bookmark)
check_logfile read the entire file on every run, so the only way to have a line
reported once was real-time monitoring, which has to be configured on the agent; the
polled path had no equivalent of the bookmarks check_eventlog already had.
check_logfile file=/var/log/app.log "filter=column1 like 'ERROR'" "warning=count > 0" bookmark=app-errors
The value is optional: bookmark, bookmark= and bookmark=auto all derive the name
from the file plus a hash of the filter, warning and critical expressions, so two
checks over one file do not consume each other's lines. Positions live in the module and
are persisted to ${data-path}/nsclient.db on shutdown.
| Behaviour | Detail |
|---|---|
| First check | Reads the file in full; from the second check on it is incremental |
| Rotation / truncation | Detected by size and by an FNV-1a fingerprint of the first bytes, so a replaced file which is already larger than the stored offset is caught |
| Unterminated last line | Held back; the position parks in front of it, so a half-written line is reported once, in full |
| A failing check | Positions are collected and only applied once every file= has been read, so an error over a later file does not consume the earlier files' lines |
| Stored positions | Bounded LRU of 1000 file/bookmark pairs; an aged-out position is blanked in the store and its file read in full the next time that name appears |
No bookmark |
Neither reads nor advances any position — behaviour is exactly as before |
Two costs are documented rather than hidden: a line is consumed when the check runs,
not when its result is submitted (so a failed passive submission does not get a second
chance), and positions are saved on a clean shutdown, so a crash re-reports the backlog
rather than losing it. ${total} counts the lines examined, not the lines in the file.
CheckLogFile — look at only the newest lines (max-lines, newest)
check_logfile file=/var/log/app.log "filter=column1 like 'ERROR'" "warning=count > 0" max-lines=100
| Option | Meaning |
|---|---|
max-lines=N |
Examine only the newest N lines of each file |
newest=last |
Newest line is at the end of the file (default; what machine-written logs do) |
newest=first |
File is rewritten with the newest line on top, as hand-maintained changelogs often are |
Selected lines are always matched in file order, so %(list) reads the way the file
does. The limit bounds the reading, not just the matching. A line-split value which
can overlap itself (aaa, --) cannot be located from the end, so those files fall
back to being read in full with the surplus dropped — same result, more I/O.
max-lines combines with bookmark to cap how much of a burst is reported (the
dropped lines are consumed, not deferred); newest=first is rejected together with a
bookmark, since a file rewritten from the top changes its fingerprint on every write.
Settings — query parameters on http(s) urls
[settings]
1 = http://nsclient.mydom.local/nsclient.php?RootFolder=myhost/&Filename=nsclient.ininet::parse split the query off into url.query, but settings_http only handed
url.path to the downloader, so the request went out as a bare GET /nsclient.php.
The new net::url::get_request_path() reassembles path and query for the request line,
and proxied requests build their absolute URI from the same path.
Caching followed: the cache file name derived from the path alone, so two entries
pointing at the same script with different parameters overwrote each other. The query
now contributes a short digest (? and & are not legal in a Windows file name), and
a url with no file name (http://host/?file=x) falls back to cached.ini instead of
collapsing onto the cache directory. Since cache_remote_file falls back to the cached
copy when the settings server is unreachable, an existing cache file is migrated to the
new name once on first start — otherwise an agent upgrading while its server was down
would have booted with an empty configuration.
The query is percent-encoded per RFC 3986 before it reaches the wire (a space produced
a malformed three-token request line; a CR or LF split one request into two). An
existing %XX is passed through untouched, and a % introducing no valid pair is
escaped.
Settings — host name placeholders
[settings]
1 = http://cfgsrv/nsclient.php?host=${hostname}Settings urls now run through socket_helpers::expand_hostname, the same helper the
submit clients (NRDP, Graphite, Syslog, Icinga, …) use for their hostname setting, so
the placeholders mean the same thing wherever they appear.
| Placeholder | Expands to |
|---|---|
${hostname} |
the system host name as reported, e.g. srv01.example.com |
${host} |
the part before the first ., e.g. srv01 |
${domain} |
the part after the first ., e.g. example.com |
Each has a _lc and _uc variant. ${hostname} / ${hostname_lc} / ${hostname_uc}
are new — expand_hostname only had ${host} and ${domain}, so a template had no way
to ask for the name as reported. This is additive (the token was previously left in place
as literal text) and every module using expand_hostname picks it up, which is the
intent. Expansion happens before parsing, so a placeholder may sit in the query, the path
or the host, and before percent-encoding, so a host name needing an escape gets one.
Attachment urls and the cache file name use the expanded url, so each host caches its own
configuration.
Settings — keep the url out of the log
The documentation promised that NSClient++ logs settings urls as scheme, host and path
only, but only the two TLS warnings in settings_http went through
to_log_safe_string(). boot() echoed the raw boot.ini entry three times — once at
info level — and get_info(), which nscp settings --show prints, embedded the raw
context. An operator who put a token in a settings url on the strength of that paragraph
got it in the log file on every boot.
url::get_baseurl(), url::get_path() and url::to_log_safe_string() are now used for
the "Activating" / "Failed to activate" / "using that" messages, the boot order list, the
"Undefined settings protocol" exception and get_info(). to_string() keeps the query
and remains the faithful rendering. The docs now also note that only the agent's own
output is covered — not a proxy, and not the settings server's access log.
Networking — detect the platform CA bundle
${ca-path} was one hardcoded path for every non-Windows platform,
/etc/ssl/certs/ca-certificates.crt. On RHEL-family the bundle is
/etc/pki/tls/certs/ca-bundle.crt and on SUSE /etc/ssl/ca-bundle.pem, so the token
named a file that is not there:
Failed to load CA /etc/ssl/certs/ca-certificates.crt: No such file
This was not limited to public hosts — make_context loads the CA whenever ca is
non-empty, before the verify mode is considered, so a check against a local self-signed
server with verify=none failed too. It also took nscp enroll with it, since 0.16.0
defaults --ca to this token, making fleet enrollment impossible on RHEL without naming
a bundle by hand.
The path is now de...
0.16.0
New database, container and system checks
This release widens what NSClient++ can watch (MySQL and MariaDB, a rebuilt Docker module, six new system checks, and a disk-fill projection that alerts on the trend rather than the threshold).
✨ Highlights
- 🐬 MySQL, MariaDB and Percona monitoring. A new CheckMySQL module adds
check_mysqlfor reachability, version, uptime and connection-pool pressure, andcheck_mysql_queryfor thresholding arbitrary SQL — every returned column becomes a filter keyword. - 🐳 CheckDocker rebuilt, from one command to five. Alongside a much richer
check_docker, there is nowcheck_docker_info(daemon health),check_docker_stats(per-container CPU and memory against the limit),check_docker_restarts(crash loops and OOM kills) andcheck_docker_df(disk usage and reclaimable space). The module now ships in the Windows MSI. - 🖥️ Six new system checks.
check_hostname,check_installed_softwareandcheck_kernel_memoryland on both platforms,check_hardwareon Windows, andcheck_loadandcheck_kernel_statsbring Windows up to parity with Unix — including synthesised load averages, the saturation signal Windows has never exposed. - 📈 Disk monitoring that predicts instead of reacts.
check_drivesizegainsfull_in,rate,trend_spanandtrend_samples, projecting time-to-full from a least-squares fit over a configurable window, so a drive that will fill on Saturday can alert on Wednesday. - 🌐 Richer network checks. Jitter from
check_pingandcheck_ntp_offset, TLS certificate expiry fromcheck_tcp, anaddress-familyflag across the network checks, ICMP payload size and TTL control, and a parsed SSH identification string. - 🧵 A thread-safety and plugin-lifecycle pass. Races in plugin dispatch, the scheduler, the web token store and the PDH collector, plus spinning and early-start bugs in the log-file and event-log watchers.
🔍 Detailed changes
🐬 CheckMySQL — new module for MySQL, MariaDB and Percona
Two commands, built against MariaDB Connector/C:
| Command | Purpose |
|---|---|
check_mysql |
Reachability and health: version, flavor, uptime, connection pool |
check_mysql_query |
Runs SQL and thresholds the rows it returns |
check_mysql keywords: version, version_comment, flavor (mysql, mariadb or percona), uptime (duration-typed, so uptime < 1h catches a restart loop), threads_connected, max_connections and connections_pct. There are no default thresholds — reachable is OK — and a connect failure is UNKNOWN with the driver's message. The USAGE privilege is enough.
check_mysql_query requires query=<SQL> and registers every result column as a filter keyword, the same way check_wmi does, plus a line keyword holding the whole row. A statement returning no result set is UNKNOWN rather than a silent OK.
Connection options on both, with defaults from /settings/mysql:
check_mysql host=db1 port=3306 user=monitor password=secret tls=true
check_mysql_query "query=SELECT COUNT(*) AS n FROM app.jobs WHERE state='stuck'" "critical=n > 0"
host=localhost forces TCP unless socket= is given, and plugin-dir= is there for MySQL 8's caching_sha2_password.
🐳 CheckDocker — modernized, and four new commands
The old module had a single check_docker with eight keywords, a hardcoded /v1.40 API prefix, and a transport that fell back to TCP and tried to DNS-resolve /var/run/docker.sock. It now speaks to the daemon over a real unix socket (or named pipe on Windows), reports transport failures as UNKNOWN with the error instead of an empty WARNING, and exposes five commands:
| Command | Reports |
|---|---|
check_docker |
Container state and health; can require named containers |
check_docker_info |
Daemon version and container/image counts |
check_docker_stats |
Per-container cpu_pct, memory_used, memory_limit, memory_pct |
check_docker_restarts |
restart_count, started, exit_code, oom_killed |
check_docker_df |
Image, container, volume and build-cache size and reclaimable space |
check_docker gains health, has_health_check, ports, labels and created keywords, resolves IPs across multiple networks, and reports a synthetic missing state for a required container that does not exist. New options are timeout=, all=true (include stopped containers) and a repeatable container=<name> with require-semantics. Only container_state != 'running' is critical by default; it used to be both warning and critical.
check_docker_restarts ships the crash-loop default outright — warning on restart_count > 3 and started < 15m and started >= 0, critical on oom_killed = 1:
check_docker_restarts
check_docker_stats container=api "critical=memory_pct > 90"
check_docker_df "warning=total_reclaimable > 10G"
The endpoint comes from /settings/docker (endpoint, timeout), defaulting to \\.\pipe\docker_engine on Windows and /var/run/docker.sock elsewhere. Podman's compat socket works.
🖥️ CheckSystem — load averages, host identity, hardware and kernel counters
| Command | Reports | Platforms |
|---|---|---|
check_load |
1/5/15-minute load averages | Windows (new), Unix |
check_hostname |
Hostname, FQDN, DNS domain and domain-join state | both (new) |
check_hardware |
BIOS, chassis and per-DIMM memory inventory | Windows (new) |
check_installed_software |
Installed-package inventory | both (new) |
check_kernel_stats |
Context switches, syscalls, process and thread counts | Windows (new), Unix |
check_kernel_memory |
Kernel pool/slab usage, file cache and page-fault rates | both (new) |
check_load brings Unix-style load averages to Windows. Utilization tells you how busy the CPUs are; load tells you how much work is queued for them, which is the saturation signal utilization alone cannot give — 100% CPU with an empty queue is a busy box, 100% with a deep queue is an overloaded one. There is no Windows equivalent to read, so the 1 Hz collector synthesises it from \System\Processor Queue Length plus cores × CPU-busy, folded into three exponential moving averages with elapsed-time-correct decay. Keywords: load1, load5, load15, load, type, queue, procs_running, procs_total, cores, samples, with percpu=true to divide the averages by core count. When the collector is not running, or has not gathered enough samples yet, the check says so as UNKNOWN rather than reporting zeros.
check_hostname reports the identity drift that silently breaks Kerberos authentication, certificate validation and monitoring host-matching. On Windows it reads GetComputerNameEx and NetGetJoinInformation — no WMI — exposing hostname, dns_hostname, domain, fqdn, join (domain, workgroup, standalone or unknown), join_name, fqdn_consistent and netbios_matches_dns; Unix exposes hostname, fqdn, domain and fqdn_consistent. Comparisons are case-insensitive, the NetBIOS check tolerates 15-character truncation, and a host with no DNS suffix is treated as consistent rather than drifting.
check_hardware answers "is this still the same machine" from WMI: vendor, model, uuid, serial, chassis, chassis_type, chassis_serial, asset_tag, memory, modules, slots, memory_speed and module_list. It is built for pinned expectations rather than thresholds — a changed serial means the box was re-imaged, cloned or replaced, and a drop in modules means a DIMM went missing:
check_hardware "critical=serial != 'CZC1234ABC'"
check_hardware "warning=modules < 4" "critical=memory < 64G"
check_installed_software inventories packages for policy enforcement and for answering "what changed just before this started". On Windows it reads the registry Uninstall hives in both the 64-bit and 32-bit views plus every loaded per-user hive under HKEY_USERS — which catches per-user installs like VS Code and JetBrains IDEs regardless of the service account, and deliberately avoids Win32_Product, whose enumeration triggers an MSI consistency check that can reconfigure every installed package on the host. Keywords are name, version, publisher, install_date, install_date_s, install_location, uninstall_string, size, hive, user, architecture, key, system_component and windows_installer; Unix reads dpkg-query, rpm -qa or pacman -Q and reports manager and status instead of the registry-specific ones. An empty result set is OK by design, which makes it a cheap absence probe, while a broken package database is UNKNOWN so it can never read as a clean pass:
check_installed_software "critical=name like 'Flash'"
check_installed_software "filter=install_date > -7d" "warning=count > 0"
Note that version comparisons are lexical, not semver, and the Windows default filter is system_component = 0 — pass filter=none to include runtime and driver components.
check_kernel_stats and check_kernel_memory cover the failure modes that free-RAM and CPU-percent thresholds miss. check_kernel_stats emits one row per metric from the PDH System set, selected with a repeatable type= (ctxt, syscalls, processes, threads), with name, label, human, rate and current per row; it is the only one of the six that ships default thresholds, warning at 8000 threads and critical at 10000 as a thread-leak guardrail. check_kernel_memory reports pool_paged, pool_nonpaged, cache, page_faults_per_sec, transition_faults_per_sec and hard_faults_per_sec on Windows, and slab, slab_reclaimable, slab_unreclaimable, cache, page_faults_per_sec and major_faults_per_sec on Unix — paged-pool exhaustion from a leaking driver and hard-fault storms are classic server failure modes that a free-memory check r...
0.15.0
SQL Server monitoring, seventeen new checks and a stall-proof system collector
This release adds a new CheckMSSQL module for monitoring Microsoft SQL Server, a large batch of new Windows checks covering disks, security hygiene and patch state, richer keywords across many existing checks, and fixes a long-standing class of collector stalls caused by slow WMI providers.
✨ Highlights
- 🗄️ New CheckMSSQL module. Five new commands monitor Microsoft SQL Server over ODBC: connectivity/health, arbitrary T-SQL queries, database state and log usage, backup age and SQL Agent jobs. Windows integrated authentication by default, with optional SQL authentication.
- 🆕 Twelve more new check commands. Disk writability (
check_disk_write), UNC share free space (check_uncpath), Storage Spaces (check_storagepool), VSS snapshots (check_shadowcopy), SMB shares (check_share), Microsoft Defender (check_defender), local account hygiene (check_local_accounts), group membership drift (check_group_members), pending reboot (check_pending_reboot), hotfix age (check_patch_age), print queues (check_printqueue) and paging I/O (check_swap_io). - ⚙️ The system collector no longer freezes on slow WMI providers. Slow every-12-second collections (network, temperature, CPU frequency, battery, OS updates) now run on their own thread, so a blocking WMI query no longer stretches
check_cputime windows or drops samples (#1378). - 📃 Multi-line check output. The new
list-separatoroption on every filter-based check lets long results render one item per line, which Nagios-compatible frontends show as summary + long output (#1370). - 🔥 check_firewall now reports the effective, group-policy-aware state. A firewall enabled or disabled through group policy previously reported its pre-policy local state (#1351).
- ⏱️ Per-disk I/O latency.
check_disk_ioandcheck_disk_healthgainread_latency,write_latencyandtotal_latencykeywords in milliseconds, on both Windows and Linux (#1369). - 🐛 Fixed
disable = cpu_frequencysilently stalling check_cpu. Disabling CPU frequency collection also disabled CPU load sampling (#1368). - 🐧 Linux packages now ship executable scripts. Bundled scripts lost their execute bit when installed by DEB/RPM packages. Thanks to Fabio Fantoni for this and for REUSE/SPDX compliance fixes.
🔍 Detailed changes
🗄️ CheckMSSQL — new module for monitoring Microsoft SQL Server
A new Windows module connecting over ODBC with Windows integrated authentication by default and optional SQL authentication (password stored as a masked settings key). The ODBC driver is auto-detected, preferring the newest "ODBC Driver NN for SQL Server" and falling back to the legacy "SQL Server" driver; on modern drivers TrustServerCertificate=yes is applied by default (overridable via trust-cert/encrypt). Login and query timeouts keep checks from ever hanging the agent, and unreachable servers report UNKNOWN with the full ODBC diagnostic chain.
| Command | Purpose |
|---|---|
check_mssql |
Connectivity and health: version, patch level, edition, uptime with time-unit thresholds |
check_mssql_query |
Arbitrary T-SQL with returned columns exposed as filter keywords and perfdata |
check_mssql_databases |
Database state, recovery model and sizes, plus log usage from DBCC SQLPERF(LOGSPACE) |
check_mssql_backup |
Age of last full/diff/log backup from msdb; never-backed-up reported as -1 and critical by default |
check_mssql_jobs |
SQL Agent job outcomes, duration and in-flight runs (is_running) |
check_mssql_backup excludes COPY_ONLY and snapshot backups by default so an ad-hoc dev backup or a VSS agent cannot mask a failing backup job (include-copy-only / include-snapshot opt back in). A new end-to-end scenario, Monitoring a SQL Server host, combines the module with service, disk, memory, PDH and event log checks and documents a low-privilege monitoring login.
check_mssql_backup "critical=full_age > 26h or full_age = -1" "warn=log_age > 2h"
💾 CheckDisk — writability probes, UNC paths, Storage Spaces, VSS and SMB shares
| Command | Purpose |
|---|---|
check_disk_write |
Verify a disk is actually writable: exclusive-create a probe file, write, read back, delete. Never touches a file it did not create; probe size capped at 1M |
check_uncpath |
Free space on a UNC path (server share), with optional alternate credentials |
check_storagepool |
Storage Spaces pool health and capacity |
check_shadowcopy |
VSS snapshot recency, count and shadow-storage usage per volume |
check_share |
List SMB shares or verify that specific required shares exist |
Existing disk checks were extended as well:
check_disk_ioandcheck_disk_healthexpose average per-I/O latency (read_latency,write_latency,total_latency, unit ms) with perfdata and metrics (#1369). On Windows the values are computed from rawPERF_AVERAGE_TIMERcounters (the formatted WMI class truncates realistic latencies to 0); on Linux from/proc/diskstats. Thresholds like"warn=total_latency > 20" "crit=total_latency > 50"work regardless of workload shape.check_drivesizegainsrequire(aliasmandatory-drives): the check goes CRITICAL if any listed drive is missing, even when scanning wildcards.check_drivesizeandcheck_disk_healthcan report physical-disk device state (health and operational status).check_filesgains aggregate file-size metrics and a folder count.
🛡️ CheckSecurity — Defender, local accounts and group membership
| Command | Purpose |
|---|---|
check_defender |
Microsoft Defender status: signature/scan age, real-time and tamper protection, engine/signature versions |
check_local_accounts |
Local account hygiene: enabled/disabled, locked, password-required/expires, built-in admin/guest |
check_group_members |
Local group membership (default Administrators) with alerting on members not on an expected allow-list |
🖥️ CheckSystem — patch state, reboot state, print queues and paging I/O
| Command | Purpose |
|---|---|
check_pending_reboot |
Whether the system is waiting for a reboot, aggregating servicing, Windows Update, file-rename, computer-rename and domain-join signals |
check_patch_age |
Installed-hotfix hygiene: time since the newest hotfix and presence of specific required hotfixes |
check_printqueue |
Print queues: queue depth, oldest-job age, offline and error states per printer |
check_swap_io |
System paging (swap) I/O rates: pages/bytes paged in and out per second |
📈 check_process — background CPU sampling, owners and more memory keywords
check_process delta=true previously sampled inside the check, slept one second and sampled again — stalling every query by a second. CPU deltas are now published by an opt-in background collector (process cpu setting, mirroring process history) that diffs the process table once a second; the check overlays a rolling per-PID CPU% onto a normal no-sleep enumeration. With the collector off, delta=true fails fast with UNKNOWN naming the setting instead of reporting misleading values, and memory/handle fields now keep their real absolute values in delta mode.
Other process-check additions: process owner resolution (with user filtering), an rss alias for working set, thread count, working set and page file percentages, peak memory keywords and system-wide thread/memory totals. Also fixed: the time keyword always reported 0 unless delta sampling was on.
➕ More keywords and options for existing checks
| Check | Addition |
|---|---|
check_network |
Per-interface packet rates, errors and discards (packets_in, packets_out, ...) with perfdata and metrics; NIC team membership (team, team_status) and WMI source keywords |
check_service |
summary option emitting aggregate state counts (running_services, stopped_services, paused_services, pending_services, service_count) for dashboard rollups |
check_os_version |
CPU architecture, Windows build revision and inventory-only BIOS fields (serial, version, manufacturer); fixed version detection for Windows 10/11 and Vista/Server 2008 |
check_os_updates |
Support for Defender definition updates and update rollups |
check_cpu_frequency |
Socket information and load percentage |
check_tasksched |
Next run time and missed-run tracking, task URI and hidden properties, default perfdata for task state and missed-run counters |
check_eventlog |
User SID retrieval and filtering; more efficient bookmark handling (plus a bookmark bug fix) |
check_pdh |
Built-in memory_pages_sec counter (\Memory\Pages/sec); more robust resolution of localized counter names |
⚙️ CheckSystem collector — no more stalls from slow WMI providers (#1378)
The background collector ran network, temperature, CPU frequency, battery and OS update collection on the same 1 Hz thread as CPU/memory/PDH sampling. The network collection queries Win32_PerfRawData_Tcpip_* via WMI with no timeout; when the WMI Performance Adapter service restarts (roughly every 16 minutes on an idle server) that query blocks for 21–24 seconds, freezing the whole collector — stretching check_cpu time windows and dropping samples. The five slow collections now run on their own thread, so a slow provider costs one stale cycle for that metric instead of a frozen collector.
The follow-up hardening fixed a subtle shared-state bug: CheckSystem, CheckEventLog and CheckLogFile all created the same named shutdown event, so stopping or reloading any one of them silently killed the others' background threads — and the name let any co-resident process signal it and disable monitoring from outside. All three now use unnamed, per-instance events with proper cleanup, and a transient ...
0.14.1
Host security posture, JSON-aware HTTP checks, and a clearer licence
This release adds a brand-new CheckSecurity module for monitoring a host's
security posture — certificates, firewall, antivirus, BitLocker, Secure Boot,
NLA and logged-on users — and teaches check_http to assert on values inside a
JSON response body. It also fixes boolean check arguments over REST, tidies up
process aggregation and module activation, relicenses the project under a clear
dual licence, and reworks the documentation to handle Windows and Linux side by
side.
Highlights
- New
CheckSecuritymodule. Seven new checks for host security posture:
check_certificate,check_firewall,check_antivirus,check_bitlocker,
check_secureboot,check_nlaandcheck_users.check_certificateand
check_usersrun everywhere; the rest are Windows-only. (#1339) check_httpcan assert on JSON responses. Newjson-path=alias:path
options extract values from a JSON body into filter keywords you can threshold
on and emit as perfdata. (#1341)- CheckNet queries now emit performance data by default, so
check_http,
check_tcpand friends graph out of the box without an explicitperf
syntax. (#1341) - Boolean check arguments accept values, not just flags.
check_ping host=www.google.com total=truenow works alongside the bare-flag
form — the form REST already used. (#1338) - Activate several modules in one command:
nscp settings --active-module CheckSystem CheckNet. (#1329-follow-up) - Clear dual licence. NSClient++ is now Apache-2.0 OR GPL-2.0-only, with
machine-readable REUSE metadata and third-party notices. (#1343) - Reworked, multi-OS documentation that presents Windows and Linux options
and features together instead of assuming one platform. (#1342)
Detailed changes
CheckSecurity — new host security-posture module
A new module, CheckSecurity (alias security), checks whether a host is in
the security state you expect. Each check is a normal modern_filter check, so
you can override the default warn/crit expressions, filter, and
detail-syntax/top-syntax as usual.
| Command | Platforms | What it checks |
|---|---|---|
check_certificate |
All | X.509 certificate expiry / validity / hygiene from files or the Windows store |
check_users |
Windows + Linux | Count and detail of logged-on / RDP sessions |
check_firewall |
Windows only | Firewall profile (Domain/Private/Public) enabled and active state |
check_antivirus |
Windows only | Registered antivirus products' enabled / up-to-date state (Security Center) |
check_bitlocker |
Windows only | BitLocker drive-encryption protection status per volume |
check_secureboot |
Windows only | Whether UEFI Secure Boot is enabled (distinguishes "disabled" from "legacy") |
check_nla |
Windows only | Network Location Awareness category (public/private/domain) per network |
check_certificate defaults to warning when a certificate expires within 30
days and critical within 10 (matching common practice), emits expires_in (whole
days until expiry) as perfdata, and can scan a whole directory:
check_certificate file=/etc/ssl/certs/mysite.pem
check_certificate file=/etc/ssl/certs recursive=true "detail-syntax=${subject}: ${expires_in}d"
check_certificate file=/etc/pki/tls/certs critical=expired=1
The Windows checks expose the raw state fields so you can tighten or relax the
default posture. check_firewall adds an active flag (which profile is
currently in effect) alongside enabled, so you can warn when a machine silently
falls back to the Public profile after a network change:
check_firewall "warn=active = 1 and profile = 'Public'" "detail-syntax=${profile} profile is active"
check_secureboot "warn=supported = 0" "crit=supported = 1 and enabled = 0"
check_nla "crit=connected = 1 and category != 'domain'" "detail-syntax=${network}=${category}"
On a platform where a Windows-only check does not apply, the check returns
UNKNOWN with a clear message rather than failing.
CheckNet — check_http JSON path extraction
check_http can now pull values out of a JSON response body and treat them as
filter keywords. Each json-path=alias:path option extracts the value at a
dotted path (numeric segments index into arrays; single-quote a segment that
itself contains a dot) and makes it available for warning=/critical=
expressions and perfdata:
check_http url=https://api.example.com/health "json-path=qlen:data.queue.length" "crit=qlen > 100"
check_http url=https://api.example.com/health "json-path=st:status" "crit=st != 'ok'"
check_http url=https://api.example.com/health "json-path=err:metrics.error_rate" "warn=err > 0.01" "crit=err > 0.05"
check_http url=https://api.example.com/health "json-path=first:items.0.name" "json-path=cfg:'a.b'.c"
Numeric values keep full precision, strings compare and render as strings, and
booleans read as 1/0. A missing path — or a body that is not valid JSON —
leaves the alias empty rather than failing the check, and multiple json-path
options can be combined freely.
CheckNet — default performance data
CheckNet queries now attach sensible performance data by default, so
check_http, check_tcp and the other network checks produce graphable
perfdata without a hand-written perf syntax. check_ntp_offset threshold
handling was also tidied up in the same change.
Check arguments — boolean options accept values
Boolean check options now accept an explicit value in addition to the bare-flag
form:
check_ping host=www.google.com total=true
Previously the value form was rejected from the CLI even though REST always
passes flags as key=true tokens, so a boolean option that worked over REST
could look broken from the command line. Both forms now behave identically.
CheckSystem — process total aggregation
check_process process-total aggregation now correctly reports the started
and hung states (on both Windows and Linux), so totals of these statuses
match what the per-process detail shows.
Settings — activate multiple modules at once
nscp settings --active-module now accepts several module names in one
invocation:
nscp settings --active-module CheckSystem CheckNet
Licensing — dual-licensed Apache-2.0 OR GPL-2.0-only
NSClient++ is now explicitly dual-licensed under Apache-2.0 OR
GPL-2.0-only. Source headers were updated to SPDX identifiers, the project
carries machine-readable REUSE metadata
(REUSE.toml, LICENSES/), and a THIRD-PARTY-NOTICES.md / NOTICE collect
the third-party licences. The installer, packaging and docs licence text were
updated to match.
Build — Python library discovery
CMake now derives the default Python library name instead of hardcoding a
version, and defaults it to the soname so the module loads without the Python
development packages installed. This makes Linux builds far less sensitive to
the exact Python version on the build and target hosts. (#1334)
Documentation
- Multi-OS reference docs. The reference documentation was reworked to
present Windows and Linux options and features together, handling checks whose
options diverge by platform instead of documenting a single OS. Windows docs
were regenerated. (#1342) - New
docs/samples/usage examples and descriptions for every new
CheckSecuritycommand and thecheck_httpJSON feature. check_processdocs cross-referencefilter_perffor top-N processes. (#1330)- README restructured and dead files removed. (#1340)
Quality and CI
- Spelling. A
codespellGitHub workflow was added and spelling errors in
log messages and settings descriptions were fixed. (#1314, #1344) - Live integration tests. A new opt-in test suite runs checks against a real
VM in Azure, alongside the existing REST-driven integration tests. New
integration tests coverCheckSecurity, thecheck_httpJSON feature, and
--active-module. (#1335) - Assorted build fixes for older Windows toolchains, Linux, and sanitizer runs.
Upgrade notes
- Licence change: NSClient++ is now distributed as Apache-2.0 OR
GPL-2.0-only. This is a clarification/relicensing — review it if your
organisation tracks the exact licence of bundled software. No code or runtime
behaviour changes as a result. - CheckNet perfdata is now on by default. Network checks emit performance
data without an explicitperfsyntax. If you were adding perfdata manually,
double-check you are not now emitting it twice; graphs that previously showed
nothing will start populating. - Boolean check arguments:
option=true/option=falsenow work from the
CLI as well as over REST. Existing bare-flag usage is unchanged. CheckSecurityis not loaded by default. Enable it before using the new
checks, e.g.nscp settings --active-module CheckSecurity. Windows-only
checks return UNKNOWN on other platforms rather than erroring.
Full Changelog: 0.14.0...0.14.1
0.14.0
Linux parity — native checks, real-time monitoring, first-class packaging, and a secure-by-default web server
This release brings Linux up to near-parity with Windows and completes the Linux story that began in 0.13.0. On the checks side it adds a full suite of Linux-native system checks (CheckSystemUnix) sourced directly from /proc and /sys, event-driven real-time monitoring on Linux, Linux disk / file / mount support in CheckDisk, and a round of cross-platform CheckNet improvements — TLS for check_tcp, a fuller check_http, multi-record-type DNS, and two new network checks. Around the daemon it delivers first-class Linux packaging (FHS layout), a secure-by-default web server, one-command installs via winget / Chocolatey / Scoop and nscp web install-ui, a Lua CLI, and a broad set of security and reliability fixes. It also hardens plugin shutdown so a misbehaving module can no longer crash the service on exit.
🌟 Highlights
- Linux system checks (
CheckSystemUnix). New native checks —check_load,check_cpu_utilization,
check_kernel_stats,check_swap_io,check_cpu_frequency,check_temperature,check_battery,check_network—
plus overhauledcheck_process(with process history / delta CPU) and a systemd-awarecheck_service. All read
/procand/sysdirectly, with thresholds and syntax that match their Windows counterparts. - Real-time monitoring on Linux.
CheckSystemUnixgains an event-driven real-time thread, so CPU, memory and
process alerts can fire the moment a threshold is crossed rather than only on poll — the same real-time model
previously available only on Windows. - Disk, file and mount checks on Linux (
CheckDisk).CheckDiskis no longer Windows-only: free-space
(check_drivesize), file (check_files) and disk-I/O checks now run on Linux, with per-device I/O sampling from
/proc/diskstats, LVM / device-mapper mapping, inode statistics, file-integrity checksums, and a newcheck_mount. - TLS-aware network checks (
CheckNet).check_tcpnow speaks TLS (ssl=true) with newSPOP/SIMAP/SSMTP
presets;check_httpgains redirect policy, certificate-expiry reporting, Basic auth, SNI and non-GET methods;
check_dnsqueries any record type against a custom resolver; and two new checks arrive —check_sshand
check_nsclient_web_online. - First-class Linux packaging. The build follows the FHS /
CMAKE_INSTALL_PREFIX, with official.deb/.rpm
targeting/usr, and a Boost.Beast web backend by default. - One-command installs everywhere. Windows via winget / Chocolatey / Scoop; the Linux web UI via
nscp web install-ui. - Secure by default. The web server refuses to serve cleartext HTTP without an explicit opt-in, plus
check_nt
command allow-listing and stricter external-script argument checks. - Run Lua scripts straight from the CLI with
nscp lua execute, backed by Lua thread-safety hardening. - Safer plugin shutdown. The plugin manager isolates broken plugins and tears modules down cleanly, so a module
that fails to unload can no longer take the service down on shutdown.
📖 Detailed changes
🐧 CheckSystemUnix — native Linux system checks
A new family of checks reads Linux kernel state directly. Thresholds and detail-syntax keywords mirror the Windows
checks so alerts port across platforms.
| Command | Source | What it reports |
|---|---|---|
check_load |
/proc/loadavg |
1/5/15-minute run-queue averages; load shortcut; percpu=true scaling |
check_cpu_utilization |
/proc/stat (~1s sample) |
Per-mode breakdown — user, system, iowait, steal, idle, total |
check_kernel_stats |
/proc/stat, /proc/loadavg |
Context-switch rate, fork/process-creation rate, live thread count |
check_swap_io |
/proc/vmstat |
Swap paging rates (swap_in/swap_out pages/s and bytes/s) |
check_cpu_frequency |
/sys cpufreq |
Current / max / min CPU frequency |
check_temperature |
thermal zones + hwmon | Thermal-zone and hwmon sensor temperatures |
check_battery |
/sys power_supply |
Charge level, power source, health |
check_network |
/proc/net/dev + sysfs |
Per-interface link status and throughput |
check_load "warn=load5 > 4" "crit=load5 > 8"
check_cpu_utilization "warn=iowait > 20" "crit=iowait > 50"
check_swap_io "warn=swap_out > 100" "crit=swap_out > 1000"
check_kernel_stats "warn=current > 8000" "crit=current > 10000"
⚙️ CheckSystemUnix — check_process history and check_service on systemd
check_processnow tracks process history and computes delta CPU between samples (rather than lifetime CPU),
and exposes memory keywords (rss,vms), matching the Windows process semantics.check_servicenow inspects systemd units. The raw systemd state is mapped to a normalisedstatekeyword so
thresholds read the same as on Windows, while the raw fields (active,sub_state,preset) are exposed too. The
default critical expression is
( state not in ('running', 'oneshot', 'static') or active = 'failed' ) and preset != 'disabled'— so a
stopped-but-disabled unit stays OK while an enabled unit that failed is CRITICAL. Per-unit process metrics
(rss,vms,cpu,tasks,age) are parsed from/procfor the unit's main process.
check_service service=cron "detail-syntax=${name}=${state} active=${active} preset=${preset}"
check_service service=mysql "warn=rss > 1G" "crit=rss > 2G"
check_os_versionnow parses/etc/os-releaseand reports the distribution and kernel details.
⚡ CheckSystemUnix — real-time monitoring
CheckSystemUnix gains a real-time collection thread and real-time data model, bringing event-driven checks to Linux.
CPU, memory and process real-time filters evaluate continuously and emit the moment a threshold is crossed, matching the
Windows real-time behaviour. See the Real-Time System Monitoring scenario, now cross-platform.
💾 CheckDisk — now on Linux: disk metrics, inodes, checksums, and check_mount
CheckDisk is no longer Windows-only. Linux builds gain the core free-space and file checks (check_drivesize,
check_files) plus disk-I/O sampling, and this release adds:
- Linux disk I/O.
check_disk_ioandcheck_disk_healthnow sample per-device I/O from/proc/diskstatsonce per
second on Linux (mirroring the Windows PDH path). LVM / device-mapper and RAID volumes are mapped back to their
backing devices via sysfs, so space and I/O join correctly for/dev/mapper/…filesystems. The first query after
startup can return UNKNOWN while the collector takes its first sample. - Inode statistics.
check_drivesizeexposesinodes_total,inodes_free,inodes_used,inodes_free_pctand
inodes_used_pct, so you can catch inode exhaustion (free bytes but no free inodes). - File-integrity checksums.
check_filesexposesmd5_checksum,sha1_checksum,sha256_checksum,
sha384_checksumandsha512_checksum, computed lazily only when referenced. check_mount(new). Verifies a filesystem is mounted — and optionally that it is mounted with the expected type
and options — reading the live mount table (/proc/self/mounts). A path that is not mounted is CRITICAL; afstype
or missing-optionsmismatch is WARNING.
check_drivesize drive=/ "warn=used>80%" "crit=used>90%"
check_drivesize drive=/ "warn=inodes_used_pct > 85" "crit=inodes_used_pct > 95"
check_files path=/var/log pattern=*.log "crit=size>100M"
check_mount mount=/data fstype=ext4
(Some Windows-only legacy CheckDisk commands are not registered on Linux.)
🔐 CheckNet — TLS for check_tcp
check_tcp can now establish a TLS session over the connected socket (ssl=true), with tls-version (default
tlsv1.2+), verify (default none) and ca options, and a response regex to match the server's greeting. Three
new TLS service presets ship alongside the existing plaintext ones:
| Preset | Port | TLS | Expected greeting |
|---|---|---|---|
SPOP |
995 | yes | ^\+OK |
SIMAP |
993 | yes | ^\* OK |
SSMTP |
465 | yes | ^220 |
check_tcp host=pop.example.com ssl=true "response=^\+OK"
check_tcp host=imap.example.com SIMAP
Peers that close the TLS session without a close_notify (reported by OpenSSL as stream_truncated) are now treated
as a clean end-of-data rather than a read failure.
🌐 CheckNet — check_http features
check_http gains the features needed for real service checks:
- Redirect policy —
onredirect=ok|follow(defaultok) withmax-redirs(default 15); follows301/302/303/307/308. - Certificate expiry — reports
ssl_expiry_days(days until the served certificate expires) for HTTPS targets. - Authentication —
username/passwordsend an HTTP BasicAuthorizationheader. - Methods and bodies —
method=(HEAD/POST/…),post-data,content-type; supplyingpost-datawith a GET
promotes the request to POST. - SNI —
sni=overrides the TLS server name / verification host.
check_http url=https://example.com method=HEAD
check_http url=https://example.com username=user password=secret
check_http url=http://example.com/old...
0.13.2
Maintenance release
A small maintenance release focused on more accurate metrics.
🌟 Highlights
- Reliable per-process CPU%.
check_processwithdelta=truenow reports correct per-process CPU usage instead of unstable or misleading values. perf-config=noneworks again. Disabling performance-data formatting no longer trips a spurious "Failed to parse syntax" error.
🐛 Fixes
- Fix unreliable per-process CPU% from
check_process delta=true. The delta calculation for per-process CPU usage produced inconsistent readings; it now returns stable, accurate values. (#1327) - Fix
perf-config=nonereporting "Failed to parse syntax". Settingperf-config=noneto suppress performance-data formatting no longer fails parsing. (#1328)
What's Changed
- Fix unreliable per-process CPU% from
check_process delta=trueby @mickem in #1327 - Fix
perf-config=nonereporting "Failed to parse syntax" by @mickem in #1328
Full Changelog: 0.13.1...0.13.2
0.13.1
0.13.0
Improved Linux compatiblity
🌟 Highlights
The headline changes in this release — see the sections below for details:
- Disk and file checks now work on Linux.
CheckDisk(check_drivesize,
check_files, disk I/O) is no longer Windows-only.
(details) - First-class Linux packaging. The build follows the FHS / install prefix,
with official.deb/.rpmfor/usr.
(details) - Easy installs everywhere. Windows via winget / Chocolatey / Scoop; the
Linux web UI vianscp web install-ui.
(details) - Secure by default. The web server refuses to serve cleartext HTTP without
an explicit opt-in, pluscheck_ntcommand allow-listing and stricter
external-script argument checks.
(details) - Run Lua scripts straight from the CLI with
nscp lua execute, backed by
Lua thread-safety hardening.
(details) - TLS improvements: outbound SNI and explicit Op5 client TLS options.
(details)
⚠️ Upgrade — please read first
A few defaults were tightened for security and the Linux packaging layout
changed. None of these affect a normal Windows MSI upgrade, but Linux users
and anyone running the web server in cleartext should read this section.
1. The web server now refuses to run unencrypted by default
To stop NSClient++ from silently serving the REST API / web UI over plain HTTP,
the WEB server now refuses to start without a certificate unless you
explicitly opt in.
If you intentionally run the web server in cleartext (e.g. behind a TLS-
terminating proxy, or on an isolated network), set:
[/settings/WEB/server]
allow insecure = trueOtherwise, provide a certificate (certificate = …). If you do nothing and the
server has no certificate, it will log an error and not start the listener.
📖 WEBServer reference
· Web interface setup
2. The web UI is a separate download on Linux (.deb / .rpm)
The Linux packages no longer bundle the React/Vite web frontend (Debian/
Fedora policy forbids npm install during package builds). The daemon, REST
API, NRPE/NSCA listeners and every check module are still in the package — only
the browser UI ships separately.
After installing the package, fetch the matching UI bundle as root:
sudo nscp web install-ui # downloads + verifies NSCP-Web-<version>.zip
sudo nscp web ui-status # show installed version / source
sudo nscp web uninstall-ui # remove only what install-ui put downUntil you do, the web port shows a small built-in placeholder page; the REST
API and all listeners work normally without it. The Windows MSI still bundles
the UI inline.
3. Linux install layout now follows the FHS / install prefix
The Linux build honours CMAKE_INSTALL_PREFIX like a normal CMake project, and
the official .deb/.rpm are built for /usr. The file layout is now:
| What | Location |
|---|---|
| Daemon | /usr/sbin/nscp |
| Modules | /usr/lib/nsclient/modules |
| Private libs | /usr/lib/nsclient |
| Config | /etc/nsclient |
| State / logs | /var/lib/nsclient · /var/log/nsclient |
If you previously patched hardcoded paths to build for a custom location,
that is no longer needed — pass -DCMAKE_INSTALL_PREFIX=/opt/nsclient (or the
standard CMAKE_INSTALL_*DIR knobs) instead. To point an already-installed
daemon at a boot.ini in a non-standard place, there is a new override:
nscp service --run --path-override boot-conf=/etc/nsclient/boot.ini📖 Choosing an install prefix
· File locations
✨ New features
Disk and file checks now work on Linux
CheckDisk is no longer Windows-only. Linux builds gain free-space, file and
disk-I/O checks, so the familiar commands work cross-platform:
# Free space on the root filesystem
check_drivesize --argument "drive=/" --argument "warn=used>80%" --argument "crit=used>90%"
# Age / size of a log file
check_files --argument "path=/var/log" --argument "pattern=*.log" --argument "crit=size>100M"(Some Windows-only legacy commands are not registered on Linux.)
📖 Disk space scenario
· CheckDisk reference
Run Lua scripts straight from the command line
Useful for developing and debugging check scripts without wiring them into the
config first:
nscp lua execute --script myscript.luaLua also got thread-safety hardening (a proper GIL), new helpers for targeted
and forwarded queries, and clearer errors when a script fails to load.
Install on Windows with winget / Chocolatey / Scoop
NSClient++ is now published to the common Windows package managers:
winget install Mickem.NSClient
choco install nsclient
scoop install nsclient # Still pending approvalTLS improvements: SNI + Op5 client TLS options
-
SNI is now sent on outbound TLS connections (Graphite and the generic TLS
client), so a TLS proxy hosting several certificates returns the right one. -
The Op5 client gained explicit TLS settings:
[/settings/op5/client/targets/default] tls version = 1.2+ verify mode = peer ca = ${ca-path}
📖 Graphite reference
· Graphite scenario
🔒 Security hardening
Beyond the cleartext-HTTP default above:
-
check_ntcan now be restricted to specific commands. The legacy
check_ntprotocol is password-only (and source-IP filtering is spoofable),
so you can now limit which of its ten request codes are answered. Default is
any(unchanged behaviour):[/settings/NSClient/server] # Answer only harmless system metrics; deny arbitrary counter/file reads # and service/process enumeration: allow = metrics, info
A request outside the list is rejected with
ERROR: Command not allowed. -
Stricter shell-metacharacter checks in external scripts. User-supplied argument
values containing more shell metacharacters are now rejected. -
Graphite metric paths are sanitized before being written to the line
protocol, preventing injection of extra metrics. -
Python
sys.pathhandling hardened to prevent code-injection via path
manipulation.
🐛 Notable fixes & reliability
- IPv6: listeners set
IPV6_V6ONLYon Linux to avoid port conflicts with
IPv4, and IPv6 address resolution was improved. - Thread-safety: logger subscriber management, the scheduler, and timer
callbacks were made properly thread-safe;CommandClientnow shuts down
gracefully on POSIX signals. - collectd client: correct (little-endian) gauge encoding, working IPv6
multicast, a configurable sendinterval(default 10s), and previously
dropped metric types (counter/derive/absolute) are now mapped instead of
discarded. - check_mk server: fixed a memory leak.
- Lua Lua log lines report the actual script line number.
📦 Packaging & distribution notes
- The bundled
check_nsclientNagios plugin moved to its own repository
(mickem/check_nsclient) and is
pulled in at build time. This only matters if you build from source. - Package/file names were normalised — double-check the exact asset name on
the releases page if you script
downloads. - Reduced Linux build dependencies: the build now uses
libzip(instead of
vendored Miniz), can use the system Google Test, and degrades cleanly when an
optional dependency is missing. Linux uses the Boost.Beast web backend by
default.
New Contributors
Full Changelog: 0.12.6...0.13.0
0.12.6
New permission system
The release has three big stories — a new core permission system with optional client-cert principals on NRPE, a
PDH overhaul that fixes long-standing counter-collection crashes and adds counter functions, and a WEB hardening
option that lets monitoring-only deployments expose the WEB UI without seeding a privileged admin account. Everything
else is bug fixes, small features, and follow-ups around those three threads.
Highlights
- Core permission system — opt-in policy layer that gates which caller can run which command. Configured under
/settings/permissions. Disabled by default; existing installs keep working.
See https://nsclient.org/docs/concepts/permissions/ for the model, identity table, and rollout recipe. - NRPE client identity from cert CN — when
client identity source = cnis set onNRPEServerand the listener
verifies the client cert, the CN is stamped as the policy principal so rules can be written per-cert (
NRPEServer:icinga-master = ...). Hard guardrail at module start refuses to load the module if the TLS verify mode
would let the CN be attacker-supplied. - Global
allow exectoggle — exec is now gated by a single on/off switch under/settings/permissions. The
per-command rule table applies to queries only. Defaulttrueso enabling the policy system does
not break exec callers. - PDH (performance counter) overhaul — fixes for service crashes when PDH misbehaves (#592, #547), counter retry
when temporarily unavailable (#634), reliable English counter lookup (#652, #906), a resource leak in the
counter-lookup path, and a refactor to smart-buffer-based PDH enumeration. Most users running CheckSystem on Windows
should see meaningfully better reliability. - check_pdh counter scaling and functions (#281) —
details-syntaxand related rendering paths can now apply
scaling and other functions, e.g.'${counter}'=${value:scale(/1024)}MB. - check_network — human-readable strings, scaling, speed, and percentages (#329); team-network statistics (#625).
See https://nsclient.org/docs/reference/check/CheckNet. - Nagios range syntax in performance data (#748) —
1:10,~:5,@10:20etc. work in perfdata thresholds,
matching the Nagios plugin spec. disable admin useron WEBServer — monitoring-only deployments can expose the WEB UI without ever seeding the
built-in admin (and previously seeded admin entries are ignored). Pairs naturally with the new permission system to
lock down reconfiguration surfaces.- Path overrides moved to
boot.ini+ new--path-overrideCLI flag — path tokens (module-path,
certificate-path, etc.) are now declared early inboot.iniso they take effect before the main config is loaded.
Per-invocation overrides via--path-override KEY=VALUE. See https://nsclient.org/docs/concepts/settings. - NRPE startup is no longer fatal on listener failure — bad bind address / port already in use logs a clear error
and leaves the module loaded so settings and commands stay usable for diagnostics. - Dual-stack listening fixed (#312) — v4 and v6 acceptors no longer trample each other's pending connection slot.
disable admin user,client identity source,allow exec, and the policy table are all documented in
https://nsclient.org/docs/concepts/permissions/ and https://nsclient.org/docs/setup/securing. Treat those two as the
starting point for any new
install.
Detailed changes
Security and permissions
Core permission system
A policy layer in the core decides whether a given caller may run a given command. Disabled by default; when enabled,
rules form a strict allow-list.
[/settings/permissions]
enabled = true
log denials = true
log allows = false ; noisy, only flip on while rolling out
allow exec = true ; queries-only rule table; exec is a global toggle
[/settings/permissions/policies]
NRPEServer = CheckHelpers.*, CheckSystem.check_cpu
WEBServer:admin = *
WEBServer:viewer = CheckSystem.check_cpu, CheckSystem.check_drivesize
Scheduler = CheckHelpers.*, CheckSystem.*
Subject is module[:principal]; object is module.command. Wildcards (*, ?) supported. Rules combine additively.
See https://nsclient.org/docs/concepts/permissions/ for the full identity model, the
CheckHelpers identity-forwarding behaviour, and a step-by-step rollout recipe.
NRPE client cert CN as principal
When two-way TLS is configured and verifying client certs against your CA, the Common Name is stamped as the policy
principal:
[/settings/NRPE/server]
client identity source = cn ; default: none
verify mode = peer-cert
ca = /etc/nsclient/ca.pem[/settings/permissions/policies]
NRPEServer:icinga-master = CheckHelpers.*, CheckSystem.*
NRPEServer:metrics-shipper = CheckSystem.check_cpu, CheckSystem.check_drivesize
Guardrails: the module refuses to start if client identity source = cn is configured without SSL, without
verify_mode containing peer and fail-if-no-peer-cert (or the peer-cert alias), or without a non-empty
ca path. The CN is logged at debug level on every accepted handshake for diagnostics. CN-only (not full DN) because
INI key syntax uses = as the key/value separator and would corrupt DN-shaped policy keys; see the "Why CN-only"
section of the permissions doc. See https://nsclient.org/docs/reference/client/NRPEServer.
Global allow exec toggle
Per-command rules apply to queries only. The exec surface (WEB scripts UI, lua/python core:simple_exec(...), CLI
exec) is gated by a single boolean:
[/settings/permissions]
allow exec = false ; hard lockdown; default is trueWhen false and enabled = true, every exec call returns
Permission denied: exec is globally disabled (/settings/permissions/allow exec = false).
See "Why exec is a single toggle" in https://nsclient.org/docs/concepts/permissions/.
disable admin user on WEBServer
For installations that expose the WEB UI for status/visualisation only and never want a remote-reconfiguration surface:
[/settings/WEB/server]
disable admin user = trueWith this set, the built-in admin is not seeded on first boot, and any existing admin entry in the user settings is
ignored at load time.
Security guide updates
https://nsclient.org/docs/setup/securing was rewritten with concrete configurations for NRPE (with
and without mTLS) and the WEB server. Read it before exposing either to a network you don't fully control.
Performance counters / PDH
The PDH subsystem (the Windows performance-counter collection backbone behind CheckSystem, check_cpu, check_pdh,
check_network, etc.) got a substantial reliability pass. Most users running NSClient++ as a long-running service on
Windows should see fewer crashes and more consistent results.
- Service crashes when PDH misbehaves on a particular machine (#592, #547) — root-caused and fixed. Misbehaving
counter registrations no longer take the service down. - Counter not retried if unavailable (#634) — counters that fail to bind at first sight now get retried on
subsequent collection cycles, instead of being permanently unhealthy for the lifetime of the process. - English counter lookup improved (#652, #906) — addresses reading of localised counters by their canonical English
names on non- English Windows installs. - Resource leak in PDH counter lookup fixed.
- PDH enumeration refactored to smart buffers — clearer memory ownership across the enumeration path, fewer footguns
for future changes. - check_pdh counter scaling and functions (#281) — all the details-syntax / rendering paths can now apply functions.
Examples:See https://nsclient.org/docs/reference/check/CheckSystem for the function reference.check_pdh "counter=\Processor(_Total)\% Processor Time" \ "details-syntax=${counter} = ${value:round(2)}%"
check_network
- Human-readable strings, scaling, speed, and percentages (#329) — perfdata and message output now render numbers in
a way operators actually want to read:check_network 'filter=interface=Ethernet' \ 'top-syntax=${list}' \ 'detail-syntax=${interface}: ${total_rx_human}/s in, ${total_tx_human}/s out' - Team network statistics (#625) — aggregate stats across Windows NIC teams.
See https://nsclient.org/docs/check/CheckNet.
Performance data formatting
- Nagios range syntax in performance data (#748) — the perfdata threshold fields now accept the standard Nagios
range syntax:5:10,~:5,@10:20, etc. Brings NSClient++ into line with what Nagios consumers already expect.
Settings, paths, and CLI
- Path overrides moved to
boot.ini— path tokens (module-path,certificate-path,data-path,log-path, …)
now live under[paths]inboot.ini(next tonscp.exe), not innsclient.ini. Overrides take effect before the
main config is loaded — including the bootstrap step that decides where the main config itself lives.; boot.ini [paths] module-path = D:\monitoring\modules certificate-path = D:\monitoring\certs
--path-overrideCLI flag — per-invocation override, repeatable. (Renamed from--pathto avoid colliding with
thenscp settings --pathsubcommand option.)nscp client --path-override module-path=/build/modules --path-override log-path=. ...- See https://nsclient.org/docs/concepts/settings for the precedence rules and the migration note for installs that had
a[/paths]section innsclient.ini.
Aliases and command registration
- CheckHelpers alias — aliases can ...
0.12.5
Windows PDH overhaul, expression functions, boot.ini paths
This release lands a long-overdue stabilisation pass on the Windows PDH subsystem (multiple long-standing crashes and
counter-availability issues), adds first-class functions in detail-syntax / warn / crit expressions, and moves
path-resolver overrides from settings into boot.ini to unblock future moves of config and certificate storage.
Highlights
- Windows PDH subsystem overhaul. Fixes #547 / #592 (service crash when PDH misbehaves on a particular machine),
#634 (counters now retried when initially unavailable instead of staying broken until restart), and #652 / #906 (
better English-counter fallback on non-English Windows). - Functions in expressions and templates (#281).
format_bytes,convert_bytes,scale, composable withand/
or/not— usable indetail-syntax,top-syntax,warn,crit, andfilter. Today exposed bycheck_pdh;
rolling out elsewhere. check_networkunderstands NIC teams (#625). Newmode=adapter/mode=bothreads
Win32_PerfRawData_Tcpip_NetworkAdapter, which is the only source that reports the team aggregate.- Aliases in
CheckHelpers. A new alias section under[/settings/check helpers/alias]provides the historical
CheckExternalScriptsalias mechanism without dragging in the external-scripts machinery. Preferred place for new
aliases. - WEB:
disable admin useroption. Suppresses the built-inadminuser entirely — for monitoring-only exposures
where remote reconfiguration must be impossible even if credentials leak. - Plugin
prepare-shutdownhook. Modules get a clean teardown phase before unload — listening sockets and pollers
stop accepting work cleanly. Wired up in the network/scheduler modules. - Path overrides moved from settings to
boot.ini.[/paths]innsclient.iniis no longer consulted; a new
[paths]section inboot.ini(and a--path KEY=VALUECLI flag) take its place. This is a breaking change for
the small number of users who relied on[/paths]— see Upgrade notes below.
Detailed changes
Windows PDH — stability overhaul
Long-standing instability in the PDH-based Windows performance-counter subsystem, addressed in one pass:
- #547 / #592 — service crash when PDH misbehaves. Hardened the enumeration and lookup paths against the partial /
inconsistent results PDH returns on certain machine states. PDH enumeration buffers were refactored to use smart
buffers throughout, removing the manual sizing loops where the bug lived. - #634 — counters now retried when initially unavailable. Previously a counter that wasn't ready at boot would stay
broken until the service was restarted; the collector now re-attempts on the normal collection cadence. - #652 / #906 — non-English Windows counter lookup. Improved the English-counter fallback path so checks that
reference counters by English name keep working on localised installs. - Resource leak in PDH counter lookup — handle leaked on the error path of counter-name → counter-path resolution.
CheckSystem — expression functions and counter scaling
#281. The expression language now supports function calls, usable in any context that takes an expression (filter,
warn, crit) or a template (detail-syntax, top-syntax, perf-syntax). Use the %(...) placeholder form —
the legacy ${...} form cannot capture nested parentheses and cannot call functions.
Built-ins exposed by check_pdh today:
| Function | Purpose |
|---|---|
format_bytes(value) |
Auto-scaled human bytes — 4194304 → "4MB" (1024-based) |
format_bytes(value, 'MB') |
Fixed unit. B, K/KB, M/MB, G/GB, T/TB |
convert_bytes(value, 'MB') |
Numeric value in the named unit — for thresholds |
scale(value, divisor) |
Divide by an arbitrary divisor (e.g. 1 000 000 for Mbps) |
# Threshold in MB, display human-friendly
check_pdh counter=memory_bytes \
"warning=convert_bytes(value, 'MB') > 500" \
"detail-syntax=%(alias) = %(format_bytes(value))"
# Network rates as Mbps (decimal — use scale, not convert_bytes)
check_pdh counter=bytes_per_sec \
"detail-syntax=Speed = %(scale(value, 1000000)) Mbps"
check_pdh also exposes variable-style shortcuts (value_human, value_mb, value_gb, …) — syntactic sugar for the
corresponding format_bytes / convert_bytes calls. Reach for variables when one of the prebuilt units fits; reach for
functions when you need a custom unit, a custom divisor, or composition with other expressions.
CheckSystem — check_network NIC team support
#625. The default mode=interface reads Win32_PerfRawData_Tcpip_NetworkInterface (one row per physical adapter —
does not report team aggregates). New modes:
mode=adapter— readsWin32_PerfRawData_Tcpip_NetworkAdapter, which includes the team aggregate as a virtual
interface named after the team. The team aggregate is the row with no matchingWin32_NetworkAdapterMAC entry, so it
can be selected withfilter=MAC = ''.mode=both— returns both sources, tagged with a newsourcekeyword for filtering.
# Monitor a NIC team aggregate
check_network mode=adapter "warn=total > 100M" "crit=total > 500M"
# Alert only on the team adapter
check_network mode=adapter "filter=MAC = ''"
CheckHelpers — aliases
Aliases (a fixed command + fixed argument list exposed under a new name) have historically lived in
[/settings/external scripts/alias], requiring CheckExternalScripts to be loaded even when the alias only
invoked internal commands. A new section under [/settings/check helpers/alias] provides the same mechanism in
CheckHelpers, with no external-scripts dependency.
[/modules]
CheckHelpers = enabled
[/settings/check helpers/alias]
my_check_cpu = check_cpu warn=load>80 crit=load>90
my_check_process = check_process "process=$ARG1$" "crit=state != 'started'"Both modules can coexist; each reads its own section. Last-loaded wins on name collisions — pick one as the home for
new aliases so you don't have to remember which is which.
WEBServer — disable admin user (cccc14e4)
New boolean under [/settings/WEB/server] that suppresses the built-in admin user entirely: it is not seeded on first
boot, any pre-existing admin row in [/settings/WEB/server/users] is dropped at load time, and the "no users → re-add
admin" fallback is skipped. For monitoring-only WEB exposures where remote reconfiguration must be impossible even if
credentials leak.
[/settings/WEB/server]
disable admin user = true
[/settings/WEB/server/users/readonly]
password = ...
role = monitoringMirrored on the install command:
nscp web install --disable-admin
Mutually exclusive with --password (the install would create no user, so a password would have nowhere to go — the
command refuses explicitly).
Service — prepare_shutdown plugin hook
Plugins now receive a prepare_shutdown callback before unload, giving them a chance to flush state, stop accepting
new work, and tear down listening sockets cleanly rather than racing the unload. Wired up in NRPEServer, NSCAServer,
NSClientServer, CheckMKServer, WEBServer, and Scheduler. The callback is optional — custom plugins built against
the older API continue to work unchanged.
Service — path overrides via boot.ini and --path CLI (fbdfe257, d2075b99)
Path-resolver tokens (module-path, certificate-path, log-path, cache-path, scripts, web-path, …) used to be
overridden via [/paths] in nsclient.ini. That doesn't work for the upcoming move of writable state out of the
install directory: the path resolver is needed before the main INI is opened, so overriding where the INI lives must
happen earlier.
The override location is now boot.ini:
; boot.ini
[settings]
common = ini://${shared-path}/nsclient.ini
[paths]
module-path = C:\Program Files\NSClient++\modules
log-path = D:\nscp\logs
cache-path = D:\nscp\cacheA --path KEY=VALUE CLI flag layers on top of boot.ini and wins — useful for build tooling and CI:
nscp service --run \
--path module-path=C:\build\modules \
--path log-path=C:\build\log
IcingaClient — built-in alias and container test
Adds a built-in alias for the standard Icinga submission flow and a Docker-based end-to-end test so the integration is
exercised on every build.
simpleini — NUL-termination fix for non-UTF-8 INI files
The INI loader passed an explicit length to mbstowcs, but per POSIX mbstowcs(NULL, src, n) ignores n and scans
until \0. On non-UTF-8 stores the size probe could walk past the buffer. The buffer now carries an explicit
terminator.
Upgrade notes
[/paths]users: if you had a[/paths]section in yournsclient.ini, copy the entries into[paths]in
boot.ini. The settings-side section is no longer consulted. The default install does not use[/paths]and is
unaffected.- Custom-plugin authors: the new
prepare_shutdowncallback is optional. If your module manages sockets or
background threads, you should implement it —unloadis now expected to be a last-resort teardown rather than the
place where listeners get stopped. check_pdhconfigs using${...}for function calls: there are none today (the feature is new), but if you adapt
examples from third-party docs that use${format_bytes(...)}, rewrite to%(format_bytes(...)). The${...}form
stops at the first}and cannot parse nested parentheses.
...