Docs
How Nominal works
A monitor is a row in your database. On an interval, a probe runs a check, conditions decide if that was healthy, and the result is stored. Alerts, status pages, and badges read the same records.
The loop
The scheduler looks for monitors whose next_check_at is due. For each assigned probe it enqueues a job on that probe’s queue (checks.local, checks.us-east, …). A worker with matching PROBE_REGION runs the check for that type.
After the probe returns, Nominal evaluates every condition against placeholders such as [STATUS] and [CONNECTED]. All of them must pass. The check result is written, consecutive success/failure counters update, and the monitor’s status becomes Up or Down. If the monitor is not in maintenance, alerting may fire.
Heartbeats skip the outbound probe. The same scheduler still “checks” whether a ping arrived in time.
What you configure
Create monitors in the Filament admin, with GraphQL (POST /graphql and a Sanctum token), or with Terraform. The admin is private. Public status is a status page, a badge, or Prometheus.
- Get started — pull the image, boot Compose, open the admin.
- Monitors — type, target, interval, and per-type request options.
- Conditions — expressions evaluated on every run.
- Alerts — channels, thresholds, reminders, and maintenance windows.
- Status pages — public pages, incidents, and badges.
Check types
Outbound types send a probe. Heartbeats wait for your jobs to call in. Database types log in with a URL and run a version/status query unless you pass your own command.
HTTP
Request a URL. Assert status, body, timing, and certificate expiry.
GraphQL
POST a GraphQL document. Same headers, TLS, redirects, and conditions as HTTP.
Ping
ICMP echo. If the network blocks ping, the probe tries TCP 443 then 80.
TCP
Dial a host and port. Optionally write a payload after connect.
DNS
Query a named resolver. Assert the rcode and the first answer.
TLS
Handshake a TLS listener that is not HTTP. Certificate expiry included.
Heartbeat
Your jobs ping a unique URL. Missed pings fail. Hung starts fail too.
UDP
Send a datagram to a host and port. The reply, if you asked for one, is `[BODY]`.
WebSocket
Dial ws:// or wss://, complete the upgrade, then optionally exchange a text frame.
MySQL
Log in with a connection URL, then run a version/status query or your SQL.
Redis
Log in with a connection URL, then PING / INFO / DBSIZE or a command you supply.
PostgreSQL
Log in with a connection URL, then list public tables or run your SQL.
Probes and regions
Each probe is a named worker region. Assign one or more to a monitor and the check runs from each. Workers listen to checks.{region}. SQLite is single-node; use Postgres or MySQL if more than one writer will touch the database. ICMP inside Docker needs cap_add: [NET_RAW] on the worker.
Where the data lives
Monitors, conditions, check history, and aggregates are ordinary tables. Query them. Scrape /metrics (prefix nominal_, labels monitor, type, success, region). Subscribe to Reverb on private-monitors for live updates; the admin also polls every 10s.
Auth
INTERFACE_AUTH is only the admin: login, none (auto operator), or cloudflare (Access JWT). GraphQL always uses a Sanctum bearer token from php artisan nominal:token.