Appearance
Website checks
The web plugin fetches a URL on a schedule and checks the status code and the page body. Turn it on to catch a site that is down, a page that carries injected code, or a page whose expected content has vanished. It can also check the origin server directly, behind a CDN, so a CDN serving cached pages can't hide a dead backend.
Each endpoint becomes its own service on the dashboard, with the unique id web-<name> (or web-<url> when the endpoint has no name).
Enable it
Add web to the plugins array. Endpoints can come from two places, and the plugin runs both at once.
Endpoints in the config file
json
{
"plugins": ["web"],
"web": {
"refreshInterval": 60000,
"endpoints": [
{
"name": "example.com",
"url": "https://www.example.com/",
"expectedStrings": ["Example Domain"],
"unexpectedStrings": ["id=\"injected-script\"", "malware.example.net"],
"timeout": 15000
},
{
"name": "example.com xmlrpc (should be blocked)",
"url": "https://www.example.com/xmlrpc.php",
"expectedStatusCode": 403
},
{
"name": "shop.example.com",
"url": "https://shop.example.com/",
"origin": {
"url": "https://origin.example.com/",
"host": "shop.example.com",
"query": { "cf": "1" },
"insecure": true
}
}
]
}
}Instead of endpoints, a single endpoint can sit at the top level of the web section (name, url, expectedStrings, unexpectedStrings, expectedStatusCode, timeout, headers, origin). The shorthand does not carry originOnly.
Endpoints managed in the dashboard
Monitors can also live in the web_monitors table and be edited under Settings, Web Monitors. Each monitor belongs to one system. The client running that system pulls its enabled monitors from GET /api/monitors/for-client, authenticated by the x-system-key header.
The plugin only does this when the web section names both the endpoint and the key. The client does not copy the connector's key in for you, so repeat it here.
json
{
"plugins": ["web"],
"web": {
"refreshInterval": 60000,
"configUrl": "https://daemonitor.example.com/api/monitors/for-client",
"systemKey": "YOUR_SYSTEM_KEY"
}
}The plugin fetches the list at the start of every pass. When both sources define an endpoint with the same name and url, the dashboard copy wins. Endpoints that only exist in the config file keep running alongside. If a fetch fails, the plugin keeps running the last list it fetched. If the very first fetch fails, only the local endpoints run.
The settings form can set the name, URL, system, expected status code and the origin check (URL, Host override, ?cf=1, ignore TLS errors). It has no fields for expectedStrings, unexpectedStrings, timeout or originOnly. The table's config column holds them and for-client passes them through, but saving the form rebuilds config from the form fields alone, so editing a monitor in the UI drops them.
Options
Section options:
| Option | Default | What it does |
|---|---|---|
refreshInterval | 60000 | Milliseconds between passes. |
endpoints | none | Array of endpoints, below. |
configUrl | none | URL of /api/monitors/for-client. Needs systemKey. |
systemKey | none | The system key sent as x-system-key to configUrl. |
Endpoint options:
| Option | Default | What it does |
|---|---|---|
name | the URL | Display name. Also used in the unique id and in alert titles. |
url | required | The public URL. Redirects are followed. |
expectedStatusCode | any 200 to 399 | Exact status the response must have. |
expectedStrings | [] | Substrings that must appear in the body. A missing one is treated as possible defacement. |
unexpectedStrings | [] | Substrings that must not appear. A match is treated as possible compromise. |
timeout | 30000 | Milliseconds before the public request is aborted. Covers the body as well as the headers. |
headers | none | Headers sent on the public request, for example a key a WAF skip rule matches. Config file only, for-client does not pass it. |
origin | none | A second, direct request to the origin server, below. |
originOnly | false | Skip the public request and report the origin check as the endpoint's health. Use it when a bot challenge blocks the checker on the public URL. Needs origin.url. |
Origin options:
| Option | Default | What it does |
|---|---|---|
url | required | An address that reaches the origin directly, by hostname or IP. |
host | none | Host header to send, so the origin's vhost routes the request like public traffic. |
headers | none | Extra headers for the origin request. |
query | none | Query parameters added to the origin URL. |
insecure | false | Skip TLS certificate checks on the origin request. |
timeout | endpoint timeout, else 10000 | Milliseconds before the origin request is abandoned. |
expectedStrings, unexpectedStrings, expectedStatusCode | the endpoint's values | Override how the origin response is judged. |
The web_monitors table also has an interval_ms column, 120000 by default. The plugin does not read it. refreshInterval sets the pace for every endpoint.
What it reports
One payload per endpoint:
name,url. After redirects,urlis the final URL.status, the HTTP status, or0when the request failed.durationin milliseconds.ok, true when the status passed and the content checks passed. This is the public check only.redirected.hasAllExpectedStrings,hasAnyUnexpectedStrings,missingExpected,matchedUnexpected. The lists name the exact strings, so an alert can say which IOC matched.error, on a network failure or timeout.origin, when configured, with its ownurl,host,status,duration,ok,errorand the same content fields.originOnly: trueon origin-only endpoints. Their top-level fields are the origin result.
When it alerts
Web checks use the findings model. Each problem is its own alert, keyed by a stable fingerprint, and it alerts on the first report that shows it. When a later report no longer contains that finding, the server resolves it. The same finding comes back as a new alert only after the earlier one has resolved, and a repeat within 30 minutes of the resolve is recorded without paging.
The server checks these rules in order and stops at the first match, so one report raises at most one finding:
| Finding | Severity | Trigger |
|---|---|---|
| Unreachable | error | The payload has error, a connection failure or timeout. |
| Possible compromise | error | hasAnyUnexpectedStrings is true on the public check. The key includes the matched strings, so a new IOC alerts again even while an older one is open. |
| Possible defacement | error | hasAllExpectedStrings is false. The key includes the missing strings. |
| Check failed | error | ok is false for another reason, usually the wrong status code. |
| Possible compromise at origin | error | origin.hasAnyUnexpectedStrings is true while the public check passed. |
| Origin down | warning | origin.ok is false while the public check passed. Visitors are still served, probably from the CDN cache. |
One more rule sits outside the findings model. When nothing above matched and the response took more than 5000 ms, the service goes to warning with "slow". That alert goes through the normal path. It waits for a second bad report before notifying, and it resolves when the service reports online again.
On the dashboard
The card is labelled "Web check", or "Web check · origin" when an origin check is configured. Its ping slot shows duration.
- STATUS shows the HTTP status. It turns red only when
okis false and the status is outside 200 to 399. A 200 page that fails a content check keeps a green STATUS, and the CHECKS list carries the failure. - LATENCY shows
durationand is never colored. - ORIGIN and ORIGIN MS appear with an origin check. ORIGIN shows DOWN on an error and turns red whenever
origin.okis false. - The CHECKS list has an HTTP row and an ORIGIN row. A CONTENT row appears when expected strings are missing, and an UNEXPECTED row when forbidden strings matched. Both are red. No content row appears on a passing check, because the payload can't tell "all strings found" from "no strings configured".
The card's overall color comes from the status the server stored. A web check with no report for 300 seconds shows OFFLINE.
Requirements
- Network access from the client to each URL, and to each origin URL.
- For dashboard-managed monitors,
configUrlandsystemKeyin thewebsection, and the monitor assigned to this client's system.
Gotchas
- Matching is plain substring matching on the raw body. No regex, no case folding.
- A pass runs its endpoints one after another, and only one pass runs at a time. With many endpoints near their 30 second timeout, a pass can outlast
refreshInterval, and the overlapping tick is skipped. - When an origin is dialed by hostname, TLS validates against that hostname, not the
hostoverride. When dialed by IP, the certificate needs an IP SAN, or setinsecure. - An endpoint without a
nameuses its URL as the alert label. After a redirect the reportedurlchanges, and so does the label. client/README.mdlists endpoint options such asmethod,body,intervalandexpectedResponseTime. The plugin reads none of them.