Skip to content

Releases: mickem/nscp

0.16.1

0.16.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 14 Aug 18:13
c225606

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_logfile bookmarks. A bookmark option remembers how far the previous
    check read and resumes from there, so a single ERROR line 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. #561
  • check_logfile tailing. max-lines=N examines only the newest N lines, and
    newest=last|first says 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/_uc variants) expand anywhere in a settings url, which turns one
    boot.ini into 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 — and nscp enroll,
    which defaults --ca to this token since 0.16.0 — failed before opening a socket.
    The path is now detected per platform at build time.
  • run on startup for 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 by nscp 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.ini

net::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...

Read more

0.16.0

0.16.0 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 13 Aug 12:35
7acb180

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_mysql for reachability, version, uptime and connection-pool pressure, and check_mysql_query for 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 now check_docker_info (daemon health), check_docker_stats (per-container CPU and memory against the limit), check_docker_restarts (crash loops and OOM kills) and check_docker_df (disk usage and reclaimable space). The module now ships in the Windows MSI.
  • 🖥️ Six new system checks. check_hostname, check_installed_software and check_kernel_memory land on both platforms, check_hardware on Windows, and check_load and check_kernel_stats bring 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_drivesize gains full_in, rate, trend_span and trend_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_ping and check_ntp_offset, TLS certificate expiry from check_tcp, an address-family flag 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...

Read more

0.15.0

Choose a tag to compare

@github-actions github-actions released this 09 Aug 15:31
9a943cf

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_cpu time windows or drops samples (#1378).
  • 📃 Multi-line check output. The new list-separator option on every filter-based check lets long results render one item per line, which Nagios-compatible frontends show as summary + long output (#1370).
  • 🔥 check_firewall now reports the effective, group-policy-aware state. A firewall enabled or disabled through group policy previously reported its pre-policy local state (#1351).
  • ⏱️ Per-disk I/O latency. check_disk_io and check_disk_health gain read_latency, write_latency and total_latency keywords in milliseconds, on both Windows and Linux (#1369).
  • 🐛 Fixed disable = cpu_frequency silently stalling check_cpu. Disabling CPU frequency collection also disabled CPU load sampling (#1368).
  • 🐧 Linux packages now ship executable scripts. Bundled scripts lost their execute bit when installed by DEB/RPM packages. Thanks to Fabio Fantoni for this and for REUSE/SPDX compliance fixes.

🔍 Detailed changes

🗄️ CheckMSSQL — new module for monitoring Microsoft SQL Server

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

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

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

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

💾 CheckDisk — writability probes, UNC paths, Storage Spaces, VSS and SMB shares

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

Existing disk checks were extended as well:

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

🛡️ CheckSecurity — Defender, local accounts and group membership

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

🖥️ CheckSystem — patch state, reboot state, print queues and paging I/O

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

📈 check_process — background CPU sampling, owners and more memory keywords

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

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

➕ More keywords and options for existing checks

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

⚙️ CheckSystem collector — no more stalls from slow WMI providers (#1378)

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

The follow-up hardening fixed a subtle shared-state bug: CheckSystem, CheckEventLog and CheckLogFile all created the same named shutdown event, so stopping or reloading any one of them silently killed the others' background threads — and the name let any co-resident process signal it and disable monitoring from outside. All three now use unnamed, per-instance events with proper cleanup, and a transient ...

Read more

0.14.1

Choose a tag to compare

@github-actions github-actions released this 06 Jul 05:53
e31fd1f

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

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

Highlights

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

Detailed changes

CheckSecurity — new host security-posture module

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

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

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

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

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

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

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

CheckNet — check_http JSON path extraction

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

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

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

CheckNet — default performance data

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

Check arguments — boolean options accept values

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

check_ping host=www.google.com total=true

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

CheckSystem — process total aggregation

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

Settings — activate multiple modules at once

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

nscp settings --active-module CheckSystem CheckNet

Licensing — dual-licensed Apache-2.0 OR GPL-2.0-only

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

Build — Python library discovery

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

Documentation

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

Quality and CI

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

Upgrade notes

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

Full Changelog: 0.14.0...0.14.1

0.14.0

Choose a tag to compare

@github-actions github-actions released this 04 Jul 05:52
2169e3a

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

📖 Detailed changes

🐧 CheckSystemUnix — native Linux system checks

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

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

⚙️ CheckSystemUnix — check_process history and check_service on systemd

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

⚡ CheckSystemUnix — real-time monitoring

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

💾 CheckDisk — now on Linux: disk metrics, inodes, checksums, and check_mount

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

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

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

🔐 CheckNet — TLS for check_tcp

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

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

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

🌐 CheckNet — check_http features

check_http gains the features needed for real service checks:

  • Redirect policyonredirect=ok|follow (default ok) with max-redirs (default 15); follows 301/302/303/307/308.
  • Certificate expiry — reports ssl_expiry_days (days until the served certificate expires) for HTTPS targets.
  • Authenticationusername / password send an HTTP Basic Authorization header.
  • Methods and bodiesmethod= (HEAD/POST/…), post-data, content-type; supplying post-data with a GET
    promotes the request to POST.
  • SNIsni= 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...
Read more

0.13.2

0.13.2 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 01 Jul 04:49
aeeccfa

Maintenance release

A small maintenance release focused on more accurate metrics.

🌟 Highlights

  • Reliable per-process CPU%. check_process with delta=true now reports correct per-process CPU usage instead of unstable or misleading values.
  • perf-config=none works 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=none reporting "Failed to parse syntax". Setting perf-config=none to suppress performance-data formatting no longer fails parsing. (#1328)

What's Changed

  • Fix unreliable per-process CPU% from check_process delta=true by @mickem in #1327
  • Fix perf-config=none reporting "Failed to parse syntax" by @mickem in #1328

Full Changelog: 0.13.1...0.13.2

0.13.1

0.13.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 17 Jun 05:23
79c164c

What's Changed

Full Changelog: 0.13.0...0.13.1

0.13.0

0.13.0 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 08 Jun 03:55
972c01f

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/.rpm for /usr.
    (details)
  • Easy installs everywhere. Windows via winget / Chocolatey / Scoop; the
    Linux web UI via nscp web install-ui.
    (details)
  • 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.
    (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 = true

Otherwise, 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 down

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

📖 Installing on Linux

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.lua

Lua 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 approval

TLS 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_nt can now be restricted to specific commands. The legacy
    check_nt protocol is password-only (and source-IP filtering is spoofable),
    so you can now limit which of its ten request codes are answered. 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.

    📖 External scripts scenario

  • Graphite metric paths are sanitized before being written to the line
    protocol, preventing injection of extra metrics.

  • Python sys.path handling hardened to prevent code-injection via path
    manipulation.


🐛 Notable fixes & reliability

  • IPv6: listeners set IPV6_V6ONLY on Linux to avoid port conflicts with
    IPv4, and IPv6 address resolution was improved.
  • Thread-safety: logger subscriber management, the scheduler, and timer
    callbacks were made properly thread-safe; CommandClient now shuts down
    gracefully on POSIX signals.
  • collectd client: correct (little-endian) gauge encoding, working IPv6
    multicast, a configurable send interval (default 10s), and previously
    dropped metric types (counter/derive/absolute) are now mapped instead of
    discarded.
  • check_mk server: fixed a memory leak.
  • Lua Lua log lines report the actual script line number.

📦 Packaging & distribution notes

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

New Contributors

Full Changelog: 0.12.6...0.13.0

0.12.6

Choose a tag to compare

@github-actions github-actions released this 15 May 11:30
277d31b

New permission system

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


Highlights

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

Detailed changes

Security and permissions

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

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

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

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

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

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

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

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

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

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

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

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

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

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


Performance counters / PDH

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

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

check_network

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

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


Performance data formatting

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

Settings, paths, and CLI

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

Aliases and command registration

  • CheckHelpers alias — aliases can ...
Read more

0.12.5

0.12.5 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 13 May 20:00
f774742

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 with and/
    or/not — usable in detail-syntax, top-syntax, warn, crit, and filter. Today exposed by check_pdh;
    rolling out elsewhere.
  • check_network understands NIC teams (#625). New mode=adapter / mode=both reads
    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
    CheckExternalScripts alias mechanism without dragging in the external-scripts machinery. Preferred place for new
    aliases.
  • WEB: disable admin user option. Suppresses the built-in admin user entirely — for monitoring-only exposures
    where remote reconfiguration must be impossible even if credentials leak.
  • Plugin prepare-shutdown hook. 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] in nsclient.ini is no longer consulted; a new
    [paths] section in boot.ini (and a --path KEY=VALUE CLI 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 — reads Win32_PerfRawData_Tcpip_NetworkAdapter, which includes the team aggregate as a virtual
    interface named after the team. The team aggregate is the row with no matching Win32_NetworkAdapter MAC entry, so it
    can be selected with filter=MAC = ''.
  • mode=both — returns both sources, tagged with a new source keyword 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 = monitoring

Mirrored 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\cache

A --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 your nsclient.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_shutdown callback is optional. If your module manages sockets or
    background threads, you should implement it — unload is now expected to be a last-resort teardown rather than the
    place where listeners get stopped.
  • check_pdh configs 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.
    ...
Read more