Projects
BetterUptime

BetterUptime

Live

A self-hosted uptime monitoring platform. Add URLs, get alerted when they go down, track response times, manage incidents, and share public status pages.

BetterUptime is a full-stack alternative to expensive SaaS uptime monitors — self-hosted, fully owned, and built for teams who want real control over their monitoring stack. A Pusher-powered scheduler ticks every 30 seconds, enqueuing monitored URLs into a Redis stream. A dedicated worker reads the stream, pings each URL, records status and response time to PostgreSQL, and automatically opens or closes incidents on state transitions. When something goes down, alerts fire via Resend email or generic HTTP webhooks with Slack-compatible payloads. The Next.js dashboard handles everything: adding monitors, viewing response time history, managing incidents, and generating shareable public status pages. Built as a Turborepo monorepo running on Bun for fast startup and lean scripts.

Why I built this

Every uptime monitoring tool worth using costs money. The free tiers are too limited and the paid tiers are priced for enterprise. I wanted to build a genuinely complete alternative — one you could self-host, own entirely, and configure exactly how you want without a monthly bill.

Use case

Developers and small teams monitoring their own APIs, web apps, and services. You get the full picture: live status, response time history, automatic incident tracking, and a public status page you can share with users — without handing your uptime data to a third-party SaaS.

What I learned

Reliability in a monitoring system is recursive — the tool that watches your uptime has to be more reliable than the things it watches. I learned how much architectural discipline goes into building a worker pipeline that handles flaky networks, slow responses, and partial failures gracefully without producing false positives.

Where I got stuck

Incident state management was the subtlest problem. Deciding when a site is 'down' versus temporarily slow, when to open an incident, and when to confidently close it required building hysteresis into the state machine — a single failed ping shouldn't page anyone, but three consecutive failures definitely should. Getting that threshold logic right without making it noisy took significant iteration.

Stack used

TypeScriptTypeScriptNext.js 15Next.js 15ExpressExpressBunBunTurborepoTurborepoPostgreSQLPostgreSQLPrismaPrismaRedis StreamsRedis StreamsZodZodJWTResendTailwind CSSTailwind CSS