Uptime monitoring that fits in your head.
A status page, a dashboard, and a JSON API — with no build step, no framework, and no dependencies at all.
Most self-hosted status pages arrive with a package tree, a bundler, a migration runner, and a container for the database. That's a lot of moving parts to answer one question: is the site up?
Sentinel is the whole thing in about 1,200 lines. node:http serves it,
node:sqlite stores it, node:crypto secures it. npm install has nothing to
install, because there is nothing to install.
git clone https://github.com/ramsai676/sentinel.git && cd sentinel
SENTINEL_PASSWORD='choose-something-long' node src/cli.ts adduser you@example.com
node src/cli.ts add "Marketing site" https://example.com 60
node src/cli.ts servesentinel listening on http://127.0.0.1:3000
status page http://127.0.0.1:3000/
dashboard http://127.0.0.1:3000/dashboard
json api http://127.0.0.1:3000/api/status
Needs Node 24+ — Node runs the TypeScript directly, so there is no compile step.
A public status page at / — no login, safe to share during an incident.
Shows every monitor with uptime, average latency, a latency sparkline, and how
long ago it was last checked.
A dashboard at /dashboard behind session auth, for adding and removing
monitors and drilling into any one of them.
A monitor detail view with a rolled-up incident history and the raw recent checks behind it — the summary and the evidence on one page.
A JSON API at /api/status, so this can feed a CLI, a Slack bot, or someone
else's dashboard:
{
"generated_at": "2026-07-28T12:48:56.506Z",
"monitors": [
{ "id": 1, "name": "Marketing site", "url": "https://example.com",
"uptime": 100, "avg_latency": 111, "last_ok": 1, "total_checks": 240 }
]
}sentinel serve Start the server and the check scheduler
sentinel adduser <email> Create a dashboard user
sentinel add <name> <url> [interval] Add a monitor
sentinel list List monitors with their uptime
sentinel check Run every due check once, then exit
sentinel prune [days] Delete checks older than N days
sentinel check exits non-zero if anything is down, so it drops straight into
cron or a CI job if you'd rather not run a long-lived process:
*/5 * * * * cd /srv/sentinel && node src/cli.ts check || notify-oncall| Variable | Default | |
|---|---|---|
SENTINEL_DB |
sentinel.db |
Database path |
SENTINEL_PORT |
3000 |
Listen port |
SENTINEL_HOST |
127.0.0.1 |
Bind address — change to 0.0.0.0 to expose |
SENTINEL_PASSWORD |
— | Password for adduser, kept out of shell history |
Everything is derived from one append-only table. Checks are the only things written. Uptime, average latency, current state, and incidents are all queries over that table — so a summary can never drift out of sync with the observations behind it.
Incidents are computed, not stored. Consecutive failures collapse into a single incident with a start, an end, and a reason. An unresolved failure stays open rather than being back-dated once it recovers.
The scheduler ticks, monitors decide. Each monitor carries its own interval; the scheduler wakes every 15 seconds and runs only what's due. Checks in a tick run concurrently, so one slow endpoint doesn't delay the others.
A check is honest about failure. Timeouts abort through an AbortSignal,
and DNS or connection errors are recorded with the reason rather than swallowed:
✗ Bad host — no response in 50ms (fetch failed)
✓ Example — 200 in 111ms
Passwords use scrypt with a per-hash random salt, verified in constant time. A login attempt for an unknown email still runs the hash, so a bad address and a bad password take the same time and can't be told apart from outside.
Sessions are 256 bits of randomBytes, stored server-side with an
expiry, and issued in an HttpOnly; SameSite=Lax cookie.
Every dynamic value is escaped on the way into the page. There's a test that
puts <img src=x onerror=alert(1)> in a monitor name and asserts it never
reaches the browser as markup.
It binds to localhost by default. Put it behind your own TLS terminator before changing that.
npm test39 tests covering storage, auth, and the HTTP layer — uptime maths, the incident collapser, cascade deletes, password verification, session expiry, auth guards on every protected route, URL validation, XSS escaping, and the probe's handling of 2xx, 5xx, explicit expected statuses, and network failure.
The HTTP tests boot the real server on an ephemeral port and drive it with
fetch; the probe tests inject a stub, so the suite never touches the network.
One of these tests earned its keep during development: two checks recorded in the same millisecond made
ORDER BY checked_at DESCambiguous, so a monitor could report the wrong current state. Fixed with anidtie-break.
MIT — see LICENSE.
Built by Ram Sai Kandagatla.