Windows server roles get their own checks, and check messages finally read like numbers
0.17.0 adds twelve new checks — eight for IIS and Remote Desktop Services on
Windows, four for the status pages of the common web servers — and gives every
filter check control over how it renders numbers, so 140.293GB/0.983TB can
become 141.09GB/1006.85GB (or 141,09GB/1.006,85GB). Alongside that, the
filter engine stops quietly doing the wrong thing: text-versus-number
comparisons are numeric, fractional thresholds mean what they say, and an error
inside a syntax template is reported instead of rendering a blank.
Highlights
- Eight new Windows checks for IIS and Remote Desktop Services. The new
CheckWindowsAppsmodule covers IIS sites, application pools, worker
processes and HTTP.sys request queues, plus RDS CAL licensing, session
counts, per-session load and the Connection Broker counterset. - Four new web-server status checks.
check_apache_status,
check_nginx_status,check_phpfpm_statusandcheck_tomcat_statusread the
vendors' machine-readable status endpoints over HTTP(S), sharing
check_http's auth and TLS handling. - Check messages can be told how to render their numbers. Four new options —
decimals,byte-unit,decimal-separator,thousands-separator— on every
filter check and every real-time filter (#1428). Perfdata and thresholds are
untouched. - Filter comparisons between text and a bare number are now numeric.
filter=value > 90no longer matchesvalue=100as false because "100" sorts
before "90", and90 > valueevaluates at all. - Fractional thresholds stop being truncated.
count > 2.5meantcount > 3
andworking_set > 1.5gmeant1g; both now mean what they say. - Host name placeholders resolve across the whole settings subsystem —
including attachment targets and[/includes](#458) — and are sanitized
before they land in a local path 🔒. - Syslog submission works again after ten years.
SyslogClientread its
connection settings from the wrong place and sent nothing at all; a configured
syslog target will start receiving traffic on upgrade. - Check-specific filter keywords that shadowed the generic summary keywords
are renamed, with the old names kept as deprecated aliases.
Detailed changes
CheckWindowsApps — a new module for Windows server roles
A new Windows-only module carrying IIS and Remote Desktop Services checks, built
on the performance counter sets and enriched from WMI where the role's provider
is installed. The two roles share one module deliberately: every check module
statically links Boost and the filter engine, so a role earns its own DLL only
when it drags in a heavy or optional dependency (the way CheckMySQL carries
libmariadb.dll).
| Command | Reports |
|---|---|
check_iis_app_pools
| Per-pool state, uptime and recycles. CRITICAL by default when an auto-start pool is not running; a pool that has never started since boot surfaces as unknown rather than hiding.
|
check_iis_sites
| Per-site state, connections and uptime, plus requests_per_sec / bytes_per_sec behind averages=true. CRITICAL by default when an auto-start site is stopped.
|
check_iis_worker_processes
| Per-w3wp active and served requests, with the <pid>_<pool> instance name split into keywords. An empty set is OK — idle pools spin their workers down.
|
check_iis_request_queues
| Per-queue length, rejections and age, defaulting to HTTP.sys' 1000-request limit (warn > 800, critical > 1000). |
check_rds_licenses
| One record per CAL key pack from Win32_TSLicenseKeyPack: total, issued and available licences. Warns at available < 10 and total > 0, critical at available = 0 and total > 0.
|
check_rds_sessions
| Active, inactive and total session counts, all three as perfdata. No default thresholds — the interesting limits are per-farm. |
check_rds_session_load
| One record per session (Console, Services, RDP-Tcp <n>) with CPU, working set and, on session hosts, RDP protocol bytes — the per-user attribution check_process cannot give. sessions-only=true skips the session-0 aggregate.
|
check_rds_broker
| The Connection Broker counterset. Counter names vary between Windows Server versions, so the check enumerates whatever the counterset exposes and reports one record per counter instead of hard-coding names. |
A host without the role gets a clean UNKNOWN naming the missing role, not a WMI
or PDH error dump. The counter plumbing landed as a reusable gather helper that
collects a set of English counter names for every instance of an object in one
query, keeping the existing localized/English/index resolution fallback — it was
verified against live Swedish-localized counters.
CheckNet — status-page checks for the common web servers
| Command | Endpoint | Keywords |
|---|---|---|
check_apache_status
| mod_status (?auto appended automatically)
| workers, requests/s, scoreboard |
check_nginx_status
| stub_status
| active/reading/writing/waiting, cumulative accepts/handled/requests, derived dropped count |
check_phpfpm_status
| FPM status page | processes, listen queue, max_children_reached, slow requests; warns by default when requests queue up
|
check_tomcat_status
| manager status?XML=true (appended automatically)
| per-connector thread pool, request/error counters, JVM heap; defaults fire at 75%/90% pool usage |
All four share check_http's connection handling — Basic auth, TLS version /
verify / CA, timeout — and go CRITICAL by default when the endpoint is
unreachable, answers non-2xx, or serves something that is not the expected
status format. Numeric parsing pins the classic locale, so a host with a decimal
comma no longer truncates ReqPerSec at the decimal point.
Filter messages — configurable number rendering
check_drivesize reported 140.293GB/0.983TB used: two units and six decimals
in one line, with no way to change either (#1428). Every filter check now takes
four options, and real-time filters take the same values as settings keys
(decimals, byte unit, decimal separator, thousands separator),
inheritable from the default template.
| Option | Effect |
|---|---|
decimals
| Exactly N decimals. Default -1 keeps the historical "up to three, trailing zeros stripped". Capped at 15.
|
byte-unit
| Pin every byte value to one unit, B…EB.
|
decimal-separator
| Radix character — , for the European rendering.
|
thousands-separator
| Digit grouping for the integer part. |
check_drivesize drive=/ show-all=true decimals=2 byte-unit=GB
OK /: 141.09GB/1006.85GB used
check_drivesize drive=/ show-all=true decimals=2 byte-unit=GB \
decimal-separator=, thousands-separator=.
OK /: 141,09GB/1.006,85GB used
The format lives on the evaluation context, so it reaches the message only:
performance data is built from the raw values and keeps its full precision and
its . radix, and so does every number the filter grammar parses out of a
threshold — warning=used>1.5g means the same thing with a decimal comma in
force.
Three defects in the byte formatter came out of this work:
format_bytes(used, 'gb')rendered1.27055e-10, because the unit comparison
was case sensitive against an uppercase table. Units are now case insensitive
everywhere.- A unit that matched nothing fell out of the comparison having divided seven
times, renderingvalue/1024^7. An unknown unit is now reported —
Filter processing failed: format_bytes failed: Unknown byte unit: ZB— and
the same check applies inside real-time filters. format_bytes(value, '')failed to parse at all; the empty string literal is
now accepted.
The filter/where engine — comparisons that mean what they say
- Text keyword versus bare number is numeric. A string-typed keyword compared
against an unquoted number used to order lexically, or — with the operands
reversed — fail to evaluate. Both sides now compare as numbers. This covers
value/warn/crit/min/max(filter_perf,render_perf),speed
(check_network),string_value(check_registry_value) andcolumn()
(check_logfile). A value that is not a number never matches; the check logs
one warning naming it and stays a certain non-match, not UNKNOWN. Quoted
literals keep the lexical comparison, as dolike,regexp,in,
keyword-specific converters (state = 'running',age > 30m) and the
= 'unknown'/= 'never'sentinels. - Fractional numbers survive.
count > 2.5used to be rounded into the
counter's integer domain, and unit literals lost their fraction entirely
(working_set > 1.5gmeant 1g,uptime < 2.5hmeant 2h). filter_perf/render_perf/xform_perf:maxandminwere swapped.
maxread the perf-data minimum bound andminthe maximum; they now read
the bounds they name.- Template errors are reported. A function that failed inside
detail-syntax
ortop-syntaxleft the placeholder empty and said nothing; the check now
returns UNKNOWN withFilter processing failed: …. perf-config'sunit:converts instead of relabelling. On byte series
that do not auto-scale,unit:KBused to change the label only, shipping
=1536KBfor 1536 bytes. The value and the warn/crit bounds now convert.
An unrecognised unit leaves the value alone rather than dividing it by 1024⁷.
Filter keywords — the clash with the generic summary keywords is resolved
A handful of checks registered a keyword named status, count or total — the
same names as the built-in summary keywords. The check-specific value won in
filter/warning/critical and detail-syntax, while top-syntax and the
reference documentation showed the generic one. Each now has a distinct name:
| Check | Old | New |
|---|---|---|
check_cpu, check_cpu_utilization
| total
| usage
|
check_battery
| status
| battery_status
|
check_network
| status, total
| link_status, throughput
|
check_os_updates
| count
| updates
|
check_patch_age
| count
| patches
|
check_pending_reboot
| count
| signals
|
check_printjobs
| status
| job_status
|
check_printqueue
| status
| printer_status
|
check_installed_software (Linux)
| status
| package_status
|
check_activation
| status
| activation_status
|
check_docker
| status
| container_status
|
check_connections
| count, total
| connections, total_connections
|
check_dns
| count
| records
|
check_http
| status
| status_message
|
check_shadowcopy
| count
| copies
|
check_disk_health
| total
| size
|
The old names remain as undocumented deprecated aliases with unchanged
behaviour, so check_cpu "warn=total > 80" still works.
Settings — host name placeholders, and where they may land
${host}, ${hostname}, ${hostname_lc}, ${hostname_uc} and ${domain} now
resolve in attachment target paths and in [/includes], not only in settings
urls and the url an attachment is fetched from (#458). An unknown ${...} token
in a path is not an error — it resolves to the installation directory — so a
configuration like [/attachments] ${shared-path}/${host}.ini = … never failed,
it quietly wrote one file with the installation directory in its name.
🔒 Because the host name is not fully under the operator's control (DHCP, or any
local privileged process can set it), a value substituted into a path is
reduced to the characters a legal RFC-952 host name can contain: anything else
becomes _, and a dots-only value becomes _. Settings urls and the submit
clients' host name specs are unaffected. See
Security notices.
nscp settings --migrate-to (and the REST migrate) now keeps a placeholder you
pass it as-is in boot.ini while migrating into the expanded per-host file, the
way --switch already did, so the template survives on a fleet-managed machine.
Clients — submission paths that were quietly dead
- Syslog.
SyslogClientread its connection settings from the sender rather
than the target, so address, port, facility, severity and templates were all
ignored: the agent loggedUndefined facility:and sent nothing. Broken since
0.4.3 (2015).CheckMKClienthad the same defect on its query path. - SMTP. The sender's host name was read from the wrong place, so it was always
empty and the EHLO fell back tolocalhost. Setehlo-hostnameon the target
if your mail server applies HELO/EHLO policy. - Short command names. A client command shorter than eight characters —
cpu,
run— answeredException processing command line: basic_string::substr …
instead of running, in every module built on the shared client machinery
(NRPE, NSCA, NRDP, Graphite, …).
CheckSystem — check_pending_reboot says since when
The CBS and Windows Update reboot keys exist only while their reboot is queued,
so their last-write time is when the signal appeared. Two new keywords follow
check_registry's naming: written (type_date, plus written_s) and age,
duration-typed so warning=pending = 1 and age > 7d reads as seven days. The
default message gains (pending since <time>) when the time is known (#1415).
The file-rename, computer-rename and domain-join signals carry no timestamp, so
both keywords are optional: they render as unknown, compare false against every
number and emit no perfdata rather than reporting a misleading value.
Data collection — one bad field no longer sinks the cycle
- WMI.
Win32_Processor.LoadPercentageis occasionally NULL, androw::get_int
had no case for it: the type-mismatch exception escaped half-way through the
row and the collector threw away the entire cycle's clock speeds and core
counts (#1391). Optional fields can now opt intoboost::none, mandatory ones
fail with a clear<col> is NULLinstead of localized COM text, and
check_cpu_frequencyrenders a missing sample asno load samplerather than
a fabricated0. - PDH.
MaxQueueItemAgeon an idle, freshly started queue returns
PDH_CALC_NEGATIVE_DENOMINATOR, which failed a whole gather even when the
caller passedignore_errors— makingcheck_iis_request_queuesmisreport the
object as missing. Withignore_errorsthe counter is now skipped for that
tick (#642, #906); the background collector and every single-counter check
still throw, so they hear about an uncomputable counter instead of silently
reading a default.
Windows installer and file layout
A round of fixes to the modern (ProgramData) layout introduced in 0.16.2:
upgrading an enrolled host resolves every path token instead of failing;
ReadLayout gets the install folder before directories resolve; CURRENT_LAYOUT
is set through the public property setter; an upgrade of a modern host no longer
re-creates nsclient.ini in Program Files; a %ProgramData% that cannot be
resolved fails outright instead of half-applying the layout; migrated files get
the destination's ACL rather than the one they came with; resetting a renamed
tree to inherited strips the explicit ACEs; and --migrate-layout legacy
migrates to legacy instead of silently to modern.
Bug fixes
nscp settings --show --path …without a--keyused to print nothing and
exit 0; it now reportsInvalid command line please use --path and --key with showand exits non-zero.- The settings diff behind the REST
diffendpoint kept listing an edit for the
lifetime of the process after it had been written, reporting amodifiedentry
whose old value equalled its new one. - Only the pending markers the backend confirms are dropped on save, and staged
deletions are masked inhas_key. - Boolean option defaults render as
true/falsein the generated reference
instead of garbage.
Documentation
Options shared by every filter check (filter, warning, top-syntax, …) and
the generic filter keywords are now single-sourced: they fold out of each
command's reference page into one shared page, so a command's documentation shows
only what is specific to it. Runtime-stubbed Windows-only checks are marked as
Windows only.
Upgrade notes
- Syslog starts delivering. If you have a syslog target configured, check it
still points where you want before upgrading — it has not been delivering,
and it will now. The same applies to SMTP targets, which will start announcing
this host in EHLO instead oflocalhost. - Host name placeholders in paths now resolve. Check any
${host},
${hostname}or${domain}under[/attachments]or[/includes]and remove
workarounds — such a file lands somewhere new after upgrade. 🔒 The value is
sanitized when it lands in a local path. Configurations without a host name
placeholder are unaffected. - Number rendering is opt-in, but it is all-or-nothing per check. Leave all
four options unset and messages are byte-for-byte unchanged. Set any of
them and plain float keywords move onto the number format too: withdecimals
unset they render with up to three decimals instead of the legacy
6-significant-digit form (2.71094→2.711), and large values stop rendering
scientific. A pipeline that matches float text in the message may need its
pattern relaxed. - An unknown unit in
format_bytes()now returns UNKNOWN instead of a quietly
wrong number. A syntax string with a typo'd unit will fail until the unit is
fixed. perf-configunit:on plain byte series changes the metric's magnitude. A
dashboard that compensated for the old mislabelling will see the metric drop by
the unit ratio; a graph flat at a near-zero value because of a misspelled
unit:will jump to its real magnitude.- Filter comparisons against a bare number are numeric. Review any filter that
deliberately relied on text ordering — quote the number to keep the old
behaviour. - Fractional thresholds change meaning. Whole-number thresholds are unchanged;
expressions that already used a decimal point can behave differently. maxandmininfilter_perf/render_perf/xform_perfwere swapped. A
filter that compensated needs the two names exchanged back.- Renamed filter keywords keep working through deprecated aliases, but three
default perfdata keys change because the defaultperf-confignames the renamed
keyword:check_cpu_utilization(Linux)cpu_total→cpu_usage,
check_patch_agepatch_count→patch_patches,check_pending_reboot
reboot_count→reboot_signals. Pass your ownperf-config=extra(...)with
the old name to keep the old key.check_os_updates' default output now reports
the actual number of updates instead of the matched-row count. check_pending_reboot's default message gains a suffix —
Reboot required: Windows Update (pending since 2026-08-16 09:41:12).
Notification pipelines matching the exact message text need their pattern
relaxed.nscp settings --showwithout--keynow fails. Scripts relying on the
silent success need the missing--keyadded.
Full Changelog: 0.16.4...0.17.0