Websites and domains
On a web host the agent does not stop at "nginx is running". It enumerates every site the web server actually serves and watches each one: response, certificate, DNS, and the life of the domain itself. All of it comes from one probe and needs zero configuration.
Site discovery
The vhosts probe parses the web server's real configuration, not a directory convention:
nginx -T for nginx, apache2ctl -S for Apache (and httpd -S on the RHEL family). Every
virtual host becomes an inventory fact of kind=vhost, which is also the billing unit for
sites. An IIS site on Windows lands in the same unit through the IIS probe, named by its
binding hostnames.
Pure redirect blocks are recognized for what they are: a server block that only bounces
to an absolute URL (a www redirector, the certbot pattern) is not probed as a site.
Its certificate and DNS are still watched (visitors see the redirector's certificate
during the bounce), but it does not produce a check or count as a site; the redirect
target is what gets monitored.
Every name of a server_name line is watched, but not every name is probed the same
way. The first name of the block is the site: it gets the HTTP check, the latency
metric, the backend and document-root checks below. Every other name (the www
twin, an old domain kept alive) gets what a visitor typing that name would meet: a TLS
handshake with that name, so the certificate the server actually presents for it is
checked (svc.<s>.vhost.ssl_name_ok says whether the certificate lists the name,
ssl_trusted whether a browser would trust it), and a DNS lookup. One block, one
verdict about the application; one name, one verdict about certificate and DNS. Two
quiet rules keep that honest: an alias under the same registrable domain as the site
(www.shop.example.com next to shop.example.com) that does not exist in DNS is simply
not reported (operators add such names to server_name without ever creating the
record, and that is not a dead domain), while an alias under another domain is judged
like any site; and a name that does not resolve at all does not report certificate
coverage either, since nobody can reach it.
A name the certificate does not cover is remembered on the site's inventory fact and raises certificate does not cover some site names on the host, one incident per host listing the names, rather than one incident per name.
The parse is cached against a signature of the config tree, so a fleet of hundreds of
sites does not re-run nginx -T every cycle. When the config changes, the site list
updates on the next sweep.
Each site's inventory fact carries context that answers real questions later: whether the docroot is a WordPress install, and which PHP version's pool serves the site (the agent maps every server block to its php-fpm socket and the socket to its PHP minor). On shared php-fpm pools the agent also flags a subtle trap: per-site PHP settings applied to a shared pool stick to the workers and leak across every site on that pool, and the agent reports when it sees that pattern.
The per-site check
Each site gets an HTTP check against loopback with the site's own Host header, so the
check exercises the real server block without depending on DNS or the network path. The
agent identifies itself honestly with the Wakora-Monitor/1.0 user agent, so you can
recognize (or exclude) it in access logs and security tooling.
A 4xx answer counts as alive (a removed default index is not an outage). Only 5xx or
no answer fails the check. Each check also reports svc.<server>.vhost.latency_ms per
site.
On boxes with very many sites the sweep is budgeted: up to 100 sites per cycle, with a rotating cursor and paced starts. A host with a few hundred sites is fully covered in roughly 15 minutes, without hammering php-fpm with a burst of simultaneous requests. Smaller hosts are probed every cycle.
Backends and document roots
A site is only as alive as what stands behind it, and the agent reads that from the same
configuration. For nginx, every proxy_pass, fastcgi_pass, uwsgi_pass, scgi_pass
and grpc_pass of the block (including the members of an upstream group) is resolved
and judged when it points at this host: a unix socket must exist, a local port must have a
listener (or at least answer a connection, for ports forwarded by the kernel). A backend
nobody serves sets svc.<server>.vhost.backend_missing for the site and raises site
backend is not running; the incident names the dead address. When the backend is a
local process other than PHP (a Node app, a Python service), the site's inventory fact
carries the process and its systemd unit, and the host page shows the site as served by
it. Backends on other machines are left alone: the agent judges only what it can see.
A block that serves files has a root; when that directory does not exist the site
reports svc.<server>.vhost.docroot_missing and raises site document root is
missing with the path (a deploy that moved the directory, a mount that did not come
back). Both checks belong to the first name of the block, so a site with four aliases
raises one incident, not four.
SSL certificates
HTTPS is detected by an actual TLS handshake on the site's port, because config
heuristics lie about SSL. For each HTTPS site the agent reports
svc.<server>.vhost.ssl_days_left and ssl_trusted (whether the chain verifies), with
browser-like notes on the check: self-signed, untrusted, or expired. The days-left metric
is still taken from a broken certificate, so an expired cert shows its negative age
rather than disappearing.
DNS liveness
A loopback check cannot see a lapsed domain: the site answers locally forever while the
world gets NXDOMAIN. So the agent also resolves each probed site name through the
system resolver and reports svc.<server>.vhost.dns_ok per site.
The lookup is careful in ways that matter on real fleets:
- Names are resolved rooted (with a trailing dot), so
resolv.confsearch suffixes cannot make a dead domain "resolve" through an internal wildcard zone. - NXDOMAIN counts only on the second consecutive sweep. A transient result emits nothing, so a resolver blip can neither open nor close anything.
- Transient errors (timeout, SERVFAIL) emit nothing, and five in a row abandon the sweep. A dead resolver must not paint the whole portfolio dead.
- Catch-all server names, bare hostnames, IP literals, wildcards, and local TLDs
(
.local,.lan,.testand friends) are skipped.
No third-party DNS or "is it up" API is involved. The agent asks the resolver the host already uses.
Sites that point elsewhere
The verdict is built from several signals, in order. If the DNS answer contains one of
the host's own addresses, the site is local. If it resolves into a known CDN (the
official published ranges of the major CDNs, plus CNAME chains onto CDN hostnames, both
shipped inside the signed definition, so the list updates without an agent release), the
site is offloaded. Otherwise the answer is compared with the host's public IP, which the
server tells the agent over the WebSocket (it sees the connection's source address, so
this is NAT-correct and involves no external what-is-my-ip service; an implausible
address is rejected rather than trusted). When none of that yields certainty, the agent
abstains instead of guessing. A site that points away emits
svc.<server>.vhost.offloaded=1.
This distinction keeps certificate alerting honest. Behind a CDN or a proxy, visitors see the edge certificate, while the loopback check measures the origin certificate. An offloaded site with a rotten origin cert is a housekeeping warning, not a visitor-facing emergency, and the server-side rules treat it that way. The same signal catches a domain that was dropped and re-registered by someone else while the old vhost still serves locally.
Domain lifecycle
Every swept site name is reduced to its registrable domain (a ccSLD table handles
co.uk, com.au and similar, and wildcard prefixes are stripped). For each registrable
domain the agent does an RDAP lookup and reports two metrics, tagged with the registrable
domain:
svc.<server>.vhost.domain_age_days: how long ago the domain was registered,svc.<server>.vhost.domain_days_left: days until the registration expires.
One point covers every subdomain site of that domain. The registrable part is taken from
the public suffix list, so shop.example.co.uk asks about example.co.uk, and names under
wildcard-DNS helpers (nip.io and friends) are never looked up, since the registration
belongs to someone else.
The lookup asks the registry over RDAP first and falls back to WHOIS (the registry's own server, found through IANA) where a registry has no RDAP; the answer is read by field name in the dozens of formats registries use. Results are kept on the host and refreshed on a schedule that follows the distance to expiry: once a week while more than two months remain, daily inside two months, every six hours in the last two weeks. A registry that fails to answer keeps the previous dates for an hour rather than dropping the metric, so a registry hiccup never reads as a lapsed domain. Within 30 days of expiry the host also emits an insight once a day, which shows up in the host story next to the metric.
Some registries publish no expiry date to anyone, over RDAP or WHOIS: .lv, .eu,
.de, .nl, .no, .at, .be, .ch, .hu, .es, .pt, .gr, .bg and a number
of smaller ones. Those domains are on a stop list: no lookup, no metric, and the console
says so where it would show the date. For them DNS liveness plus certificate decay remain
the honest signals. A registry whose answer the agent cannot read is reported as a fact
on the service, never as a failing check, because a format change on a registry's side
must not raise an incident on your host.
The age metric is the drop-catch detector: renewing a domain does not reset its registration date, so a young age on a long-standing site means the domain really lapsed and someone re-registered it. Negative days-left means the registration expired but the zone still answers, which is the registrar grace window: renew now.
A host whose outbound network is closed (no port 43, no path to the registries) still gets its dates: when the lookup fails for a network reason, the agent asks the platform to make that one lookup on its behalf and hands the answer back, with a daily budget per host. The query carries the domain name and nothing else.
There is also a standalone domain probe for explicitly configured domains, reporting
ext.domain.days_left through the same lookup path.
What alerts on all this
The agent measures and reports. Thresholds and incidents (certificate expiring or not covering a name, domain does not resolve, domain expired, drop-catch suspicion, backend not running, document root missing) are evaluated server-side on these metrics, so they can change without touching the agent. The exact rules are in the rules catalog.