Self-hosted uptime monitoring — a lightweight Uptime Kuma / UptimeRobot alternative built on Bun, React 19, Tailwind v4 + shadcn/ui, and SQLite. One container, one volume, no external services.
Dashboard — live overview with per-monitor uptime bars, grouped sidebar, and down alerts:
Monitor detail — recent checks, 24h/7d/30d response-time chart, uptime and TLS expiry at a glance:
Public status page — grouped components, 90-day uptime history, pinned incidents with severity styling:
- Monitor types: HTTP(s), HTTP keyword (present/absent), Ping (ICMP), TCP port, DNS (A/AAAA/CNAME/MX/TXT/NS, custom resolver), and Push (heartbeat/cron) monitors
- Checks: intervals from 20s, per-monitor timeout, retries with
PENDINGgrace state, accepted status-code ranges, follow-redirects toggle, ignore-TLS toggle, custom headers/body/method - TLS certificate expiry tracking for HTTPS monitors
- Grouping for monitors, with a live grouped sidebar
- Status pages (multiple): pick monitors into named sections, publish/unpublish, optional password protection, optional custom domain, incidents (info/warning/danger/success, pin + resolve), 90-day daily uptime bars, "Powered by" toggle
- Notifications: Webhook, Slack, Discord, Telegram, ntfy, Email (SMTP) — attach per monitor, mark channels as defaults, one-click test
- Maintenance windows: scheduled silence — checks record
MAINTENANCE, alerts are suppressed, status pages show it distinctly - MCP server: built-in Model Context Protocol endpoint so AI assistants (Claude, Cursor, …) can query status, inspect monitors, and create/pause/resume them
- Dashboard: live overview, response-time charts (24h/7d/30d), important-event log, uptime bars
- Dark/light theme, responsive UI
docker compose up -dOpen http://localhost:3001 and create your admin account on first run. Data (SQLite + WAL) lives in the vigil-data volume mounted at /app/data.
| Variable | Default | Purpose |
|---|---|---|
PORT |
3001 |
HTTP port |
DATA_DIR |
/app/data (Docker) / ./data (dev) |
SQLite location |
SESSION_SECRET |
auto-generated, persisted | Cookie/token signing secret |
TZ |
UTC |
Container timezone |
Create a Push monitor and Vigil gives you a secret URL. Have your cron job / worker ping it within the configured interval:
curl -fsS "https://vigil.example.com/api/push/<token>?status=up&ping=42&msg=OK"If no ping arrives within interval + grace, the monitor goes DOWN and alerts fire. Send status=down to fail it explicitly.
Each page is served at /status/<slug>. Options:
- Password: viewers must enter it once per browser (12h cookie).
- Custom domain: set the domain on the page, then point DNS (or your reverse proxy) at the Vigil server. Requests landing on that host render the status page at the domain root — the admin app stays available on your primary host.
- Incidents: post updates with severity styling; pin important ones; resolve when done.
Vigil ships a built-in MCP server so AI assistants can query and manage your monitors. Generate an API key under Settings → MCP Server, then point any streamable-HTTP MCP client at /mcp:
{
"mcpServers": {
"vigil": {
"type": "http",
"url": "https://vigil.example.com/mcp",
"headers": { "Authorization": "Bearer vgl_..." }
}
}
}Tools
| Area | Tools |
|---|---|
| Status | get_status_summary, list_monitors, get_monitor, list_events |
| Monitors | create_monitor, pause_monitor, resume_monitor |
| Notifications | describe_notification_types, list_notification_channels, create_notification_channel, update_notification_channel, delete_notification_channel, test_notification_channel, set_monitor_notifications |
Channel configuration is self-describing: describe_notification_types returns the JSON Schema for each provider (webhook, Slack, Discord, Telegram, ntfy, SMTP), so an assistant can build a valid config without guessing field names. Credentials are write-only — secret fields come back as *** when a channel is read, and update_notification_channel merges the keys you pass, so you can change one field without resending a secret you cannot see.
The endpoint is stateless JSON-RPC over POST (no SSE session); rotate or disable the key at any time from Settings.
bun install
bun run dev # Bun fullstack dev server with HMR on :3001
bun run typecheck # tsc --noEmit
bun test # engine unit testsThe frontend is bundled by Bun itself (HTML imports + bun-plugin-tailwind — no Vite); in production (bun run start or Docker) assets are bundled once at startup, cached, and minified.
src/
shared/types.ts # domain + API contract shared by server and web
server/ # Bun.serve() routes, bun:sqlite, scheduler, checkers, notifiers
web/ # React SPA (react-router, TanStack Query, shadcn/ui)
- Heartbeats are appended to SQLite (WAL) with transition detection; daily aggregates power 30/90-day uptime and survive raw-beat retention (configurable, default 180 days).
- Sessions are cookie-based (argon2id password hashing via
Bun.password). - Ping checks shell out to the system
ping(bundled in the Docker image).


