Skip to main content

Sensors

A sensor is a name, a type, a target, an interval, and a handful of options that depend on the type. Everything is editable in the web UI, and the same objects can be declared in YAML.

TypeTargetWhat it does
httpa URLAn HTTP request, optionally asserting on the status, headers, and body
tcphost:portA connection, optionally a TLS handshake and a send/expect exchange
tlsa host or host:portA TLS handshake, reporting how long the certificate is good for
pinga host or IPICMP echo requests, falling back to an unprivileged UDP ping
dnsa hostnameA DNS lookup against a resolver you choose, asserting on the records
dockera container nameThe state, health and restart count of a container on this host
snmpa host or host:portOne OID from a switch, router or NAS, checked against a value you set

⚙️ Common settings​

These apply to every type.

SettingDefaultBoundsMeaning
interval60≥ 5Seconds between probes
timeout101–600Seconds before a probe is abandoned and counted as failed
retries1≥ 1Consecutive failures before an incident opens and alerts fire
group—Free-text label; sensors in a group are drawn together
tags—Free-text labels for filtering and search
reminder_every0≥ 0Seconds between repeat alerts while a sensor stays down; 0 disables them
enabledtrueA disabled sensor is not probed and shows as Paused
channels—Which alert channels this sensor notifies

Values outside those bounds are clamped on save rather than rejected, so a generated configuration file with interval: 1 produces a sensor that runs every five seconds instead of a pile of overlapping probes.

🔁 About retries​

retries is a debounce, not a retry. ProbeGlass does not re-run a failed probe immediately — it counts consecutive scheduled failures and opens an incident when the count reaches the threshold. With interval: 60 and retries: 3, a service that goes down at 12:00:20 is reported at 12:02:20, and a single dropped packet at 03:00 never wakes you up.

While a sensor is in the failure streak but below the threshold, its status is Flaky — a yellow pill, not a green one and not a red one. That is deliberate: the honest answer to "is it up?" at that moment is "the last probe failed and it has not failed often enough to say it is down", and showing green would be a lie. Flaky opens no incident and sends no alert: the incident log is for outages, and a message per dropped packet is noise. It is still on the dashboard, so a sensor that flaps without ever reaching its threshold is visible rather than silent.

Pending is the fourth thing the pill can say and means something narrower: the sensor has never been probed, so there is nothing to report yet. That is what a freshly created sensor reads for the seconds before its first probe.

🌐 http​

Target: a full URL, including the scheme.

OptionTypeDefaultMeaning
methodstringGETRequest method
expected_statusstring200-399Accepted status. 200, 200-299, 2xx, or a comma-separated list
headersobjectExtra request headers
user_agentstring—Overrides the User-Agent header
basic_auth_user, basic_auth_passstring—HTTP basic authentication
bearer_tokenstring—Authorization: Bearer …
bodystring—Request body, for POST/PUT/PATCH
body_containsstring—Fail unless the response body contains this substring
body_not_containsstring—Fail if the response body contains this substring
body_regexstring—Fail unless the response body matches this regular expression
expected_headersobject—Response headers that must each contain a given substring
max_body_bytesint262144How much of the body to read
follow_redirectsbooltrueSet false to treat a redirect as the answer
insecure_skip_verifyboolfalseAccept self-signed certificates. Per sensor, never global
cert_warn_daysint—Fail when the certificate expires within this many days

body_contains and expected_status together are what turn a liveness sensor into one that means something. A reverse proxy whose backend is dead usually returns 200 OK with an error page; only the body assertion catches that.

insecure_skip_verify exists because self-signed certificates are normal in a homelab. It is never enabled globally and never inferred — if you want it, you ask for it on that sensor, and the setting is visible in the sensor's own configuration, not hidden in a global switch.

- name: Home Assistant
type: http
target: https://ha.lan:8123/manifest.json
interval: 60s
options:
insecure_skip_verify: true
body_contains: '"name":"Home Assistant"'

🔌 tcp​

Target: host:port.

OptionTypeMeaning
tlsboolPerform a TLS handshake after connecting
server_namestringSNI and verification name, when it differs from the target host
insecure_skip_verifyboolAccept self-signed certificates when tls is on
sendstringText to write after connecting. \r and \n are expanded
expectstringFail unless the response contains this substring

send/expect covers the protocol-aware sensors that a bare port sensor cannot: SMTP, IMAP, MQTT, PostgreSQL, and anything else that greets you with a banner. The response is read until expect is found or the timeout expires.

- name: SMTP greeting
type: tcp
target: mail.lan:25
interval: 5m
options:
expect: "220 "

🔒 tls​

Target: a hostname, or host:port. A bare hostname means port 443.

OptionTypeDefaultMeaning
server_namestringthe target's hostThe name to send as SNI and to verify the certificate against
cert_warn_daysint14Fail when fewer days are left; 0 switches the check off
insecure_skip_verifyboolfalseAccept any certificate; the message then says the check was skipped

HTTP and TCP sensors read a certificate as well, so this type adds no capability — it is the place to say that the certificate is the subject. The probe connects, completes a handshake, and reads the leaf certificate; the message carries the remaining days, and a certificate that has already run out fails whatever cert_warn_days says.

The three ways a certificate can be wrong are reported separately, because they are fixed in three different ways: a name that does not match the one you asked for, a certificate that has expired or is not valid yet, and an issuer this host does not trust — the last being the usual self-signed homelab appliance, which is what insecure_skip_verify is for.

- name: Home Assistant certificate
type: tls
target: ha.lan:8123
interval: 6h
options:
cert_warn_days: 21

🐳 docker​

Target: a container name or id on the host whose Docker socket is mounted into the ProbeGlass container.

OptionTypeDefaultMeaning
socketstring/var/run/docker.sockPath to the Docker socket
expect_runningbooltrueSet false to watch a container that is supposed to be stopped
expect_healthstringhealthyhealthy, starting, or unhealthy; empty switches the check off
max_restartsint0Fail above this many restarts; 0 switches the check off

The probe asks the Docker Engine API for one container's inspect output and reads four things from it: whether it is running, what state it reports, what its health check last said, and how often it has restarted. The restart count is in the message on every probe, whether or not you set a limit — seeing the number is what tells you the limit is worth setting.

expect_health is on by default, and it needs a container with a HEALTHCHECK. Most containers have none, and such a container can never report healthy, so it is reported down with the reason spelled out — together with the name of the option that switches the check off.

A socket that is missing or unreadable fails the probe with a message that says which it is, rather than failing to save the sensor: the socket belongs to the host, and it may well not be there while you are writing the sensor.

warning

Mounting the Docker socket gives the container root on the host. Read The Docker socket is root on the host before you do, and check that it is what you want.

- name: Home Assistant container
type: docker
target: homeassistant
interval: 60s
options:
max_restarts: 3

expect_health is absent above for that reason: the example watches whether the container is up and how often it has restarted, and asks nothing of a health check that may not exist. In the dashboard the same choice is the empty entry in the health dropdown, labelled "only check that it runs".

📡 snmp​

Target: a hostname or IP, optionally with a port — a bare host means port 161.

OptionTypeDefaultMeaning
oidstring—The object to ask for, dotted. Required
communitystringpublicv2c community string. A shared secret, masked in the API
versionstring2c2c or 1. v3 is not supported
expectstring—Fail unless the value contains this substring
min, maxnumber—Bounds a numeric value must lie within

This is one GET of one OID. No walk, no traps, no version 3 — a switch, a router or a NAS answers "how warm is the CPU", "how long has it been up" and "how much ink is left" with a single request each, and that is the whole job. A walk enumerates a device's entire MIB, which is a different tool with a different cost, and it is not this one.

You must give at least one of expect, min or max. That the agent answered at all is what a ping already tells you, so the value has to be judged against something you wrote down, or the sensor is a liveness check wearing a number.

OIDs are written as dotted numbers — 1.3.6.1.2.1.1.3.0 is uptime. Names such as sysUpTime.0 are refused: resolving a name needs a MIB file, and a tool that quietly guesses at one quietly watches the wrong thing. Values are shown as they arrive; non-printable ones (a MAC address is six raw bytes) are shown as hex rather than as punctuation soup, and uptime is shown as 1h 11m as well as raw.

warning

SNMP v2c is unauthenticated and the community string travels in clear text. Use it on a trusted network only — see SNMP v2c sends the community in clear text.

- name: Switch is the model I think it is
type: snmp
target: 192.168.1.2
interval: 5m
options:
oid: 1.3.6.1.2.1.1.1.0
community: homelab-ro
expect: RB5009

📡 ping​

Target: a hostname or IP address.

OptionTypeDefaultMeaning
countint3Echo requests per probe, up to 20

The sensor tries an unprivileged datagram ICMP socket first and falls back to a raw socket. In a container this is where things go wrong most often: without NET_RAW, and with net.ipv4.ping_group_range not covering the container's uid, neither socket can be opened. When that happens the sensor fails with a message saying precisely which socket it tried and what the kernel said, rather than a bare timeout that sends you looking at the network.

Two failures that used to read the same are told apart, because they send you to different places: a probe that left this host and got nothing back says no ICMP reply from … (3/3 lost) and the network is the suspect, while a probe whose socket refused to send at all says could not send ICMP to … and names the kernel's reason — with the NET_RAW hint appended when that is what it was.

🧭 dns​

Target: a hostname to look up.

OptionTypeDefaultMeaning
record_typestringAA, AAAA, CNAME, MX, TXT, NS, SRV, or PTR
resolverstringsystem1.1.1.1 or 192.168.1.1:53 — the server to ask
expectedstring—Comma-separated substrings the answers must contain
expect_allboolfalseRequire every item in expected, not just one

Queries are built with golang.org/x/net/dns/dnsmessage rather than the system resolver, so the sensor asks a server you name and inspects the records that actually came back. That is the difference between "DNS works" and "this internal name resolves to the address I expect from the resolver my clients use":

- name: Nextcloud resolves internally
type: dns
target: cloud.lan
interval: 5m
options:
resolver: 192.168.1.1:53
record_type: A
expected: 192.168.1.42

✅ Validation​

Every engine validates its own target and options, and the server runs the same validation when you save. The Test button in the sensor form additionally probes once and shows you the real latency, status code, and message.

A malformed option does not fail the sensor silently.

What happens to an option of the wrong type

Options are read through typed accessors that fall back to the default for anything missing or of the wrong type, so a sensor saved by an older version keeps working — a count that arrives as a string becomes the default rather than becoming zero. Anything that is a genuine configuration error is reported at the point you can fix it.

📄 Sensors from the configuration file​

A sensor declared in the YAML configuration is marked managed in the UI: its edit, pause, reorder, and delete buttons are disabled, because the file wins. It is re-applied on every start, so a rebuild from zero restores the whole monitoring setup in seconds. See Declarative sensors and channels.