Skip to content

Repository files navigation

📡 PingBeacon

100% free & open source uptime monitoring. Monitor any HTTP(S) endpoint, get public status pages with 90-day uptime bars, and receive Slack + email alerts the second something breaks.

No plans. No paywalls. No limits. Unlimited monitors, any check interval, forever — on the hosted instance or self-hosted (MIT license).

Everything runs on free tiers:

Piece Tech Cost
App + API routes Next.js 14 (App Router) + TypeScript + Tailwind
Database & Auth Supabase (free tier) + Row Level Security $0
Check engine GitHub Actions cron (any schedule your repo allows) $0
Hosting Vercel (Hobby) $0
Alerts Slack incoming webhooks + Resend (free tier) $0

Screenshots

Landing page Public status page
Landing page Public status page
Dashboard Monitor detail
Dashboard Monitor detail

Architecture

GitHub Actions (cron)
        │  POST /api/cron/run-checks   (header: x-cron-secret)
        ▼
Vercel  └─ Next.js route ── service-role client ──▶ Supabase
                                                      │
   probes URL, writes monitor_checks                   │ opens/resolves incidents
        │                                            │ sends alerts
        └──▶ Slack webhook / Resend email              │
                                                      ▼
        /status/[username]  ◀── reads via service role (no auth)
  • Check worker — GitHub Actions schedules POST /api/cron/run-checks. The route verifies the x-cron-secret header against CRON_SECRET, then probes every active monitor that is due. When a monitor is down and has no open incident it opens one + alerts; when it recovers it resolves the incident + sends a recovery alert.
  • No limits, by design — the plan-limit trigger and Stripe billing were removed in 010_open_source_unlock.sql (and the stripe package dropped from the codebase). The plans/subscriptions tables remain as metadata only; nothing gates on them. Every account gets unlimited monitors.
  • Public status page/status/[username] is public. It reads data with the service-role key server-side and auto-refreshes every 30 seconds via GET /api/status/[username].
  • Free-tier note — GitHub Actions only schedules workflows every 5 minutes or coarser on its free plan. The workflow ships with */5 * * * *. If your repo allows a faster schedule, switch to * * * * * for every-minute checks. Monitors also gate themselves by interval_minutes, so nothing is double-checked.

Project layout

.
├── .env.example                     # all env vars, documented
├── .github/workflows/uptime-check.yml
├── supabase/migrations/
│   ├── 001_initial.sql              # schema, RLS, triggers, uptime aggregates
│   ├── 002_saas.sql                 # plans, subscriptions (metadata only)
│   ├── 003_v2_phase1.sql            # status_pages, subscribers, maintenance, push
│   ├── 004–009_v2_*.sql             # v2 features, security hardening, premium
│   └── 010_open_source_unlock.sql   # drops plan-limit trigger → 100% free
├── src/
│   ├── middleware.ts                # session refresh
│   ├── app/
│   │   ├── layout.tsx / page.tsx    # root layout (PWA manifest) + landing
│   │   ├── login / signup / auth/callback
│   │   ├── dashboard/
│   │   │   ├── layout.tsx           # auth guard + nav
│   │   │   ├── page.tsx             # monitor list + 30d stats + subscriber count
│   │   │   ├── monitors/new         # add monitor form
│   │   │   ├── monitors/[id]        # chart, uptime bars, incidents
│   │   │   ├── billing              # open source page (no plans)
│   │   │   ├── maintenance          # schedule planned downtime
│   │   │   └── settings             # alert channels + push notifications
│   │   ├── status/[username]        # public status page (+ subscribe form)
│   │   └── api/
│   │       ├── cron/run-checks      # the check worker entrypoint
│   │       ├── cron/digest          # weekly digest email (Mon 9am)
│   │       ├── status/[username]    # public JSON status API
│   │       ├── status/[username]/subscribe   # add a status page subscriber
│   │       ├── status/[username]/confirm     # confirm a subscription
│   │       ├── push/subscribe       # save a Web Push subscription
│   │       ├── push/unsubscribe     # remove a Web Push subscription
│   │       ├── push/vapid-public-key# expose the VAPID public key
│   ├── components/                  # UI pieces (Tailwind only)
│   └── lib/                         # supabase clients, checks, alerts, subscribers, push, digest

Local demo stack (no cloud accounts required)

You can run the entire SaaS locally with zero cloud sign-ups: a local PostgreSQL (Supabase-compatible) plus a small Node shim that speaks the /auth/v1 + /rest/v1 Supabase API against it. Auth, Row Level Security, incidents and alerts all work exactly as they do in production — only Slack/Resend delivery is skipped when keys are absent (each fails gracefully and logs).

Requires: Homebrew (brew install postgresql@16 postgrest) and Node 18+.

# 1. Build the stack (initdb, migrations, secrets → .env.local)
.local/stack.sh setup

# 2. Start postgres + PostgREST + shim
.local/stack.sh start          # status / stop / reset also available

# 3. Run the app (uses .env.local)
npm run dev                    # → http://localhost:3000

What the shim provides:

Endpoint Purpose
POST /auth/v1/signup real signup → auto profile + subscription
POST /auth/v1/token password + refresh grants, real JWT
GET /auth/v1/user session lookup used by middleware
POST /rest/v1/* pass-through to PostgREST (RLS + RPCs)

A good end-to-end demo: sign up, add a monitor pointing at http://127.0.0.1:59999 (everything is unlocked — no plans), then:

curl -X POST http://localhost:3000/api/cron/run-checks \
  -H "x-cron-secret: local-cron-secret"

The dead port goes down, an incident opens and Slack/email alert code runs. Point the monitor at a healthy URL, wait for its interval, run again — the incident resolves. Watch it all on /status/<username> and in the dashboard.


Setup

1. Create the app

git clone https://github.com/you/pingbeacon.git
cd pingbeacon
npm install
cp .env.example .env.local

2. Set up Supabase (free)

  1. Create a project at https://supabase.com.
  2. In the SQL Editor, paste the contents of supabase/migrations/001_initial.sql and run it. (If you have the Supabase CLI, supabase db push also works.)
  3. Project Settings → Authentication → URL Configuration:
    • Site URL: your Vercel URL (or http://localhost:3000 in dev)
    • Redirect URLs: add http://localhost:3000/auth/callback and https://<your-app>.vercel.app/auth/callback.
  4. Under Authentication → Providers, confirm Email is enabled.

Uptime stats and the 90-day bars are computed by SQL aggregate functions (get_uptime_bars_many, get_monitor_stats_many) so the app never pulls thousands of raw check rows — no max rows change is needed. The only raw fetch is the 24h response-time chart; if you check at 1-minute intervals you may optionally raise Settings → API → Max rows per request to 10000.

3. Fill in .env.local

NEXT_PUBLIC_SUPABASE_URL=      # Project Settings → API → Project URL
NEXT_PUBLIC_SUPABASE_ANON_KEY= # Project Settings → API → anon public key
SUPABASE_SERVICE_ROLE_KEY=     # Project Settings → API → service_role (server only!)
CRON_SECRET=                   # run: openssl rand -hex 32
RESEND_API_KEY=                # https://resend.com → API Keys (free)
RESEND_FROM_EMAIL=             # verified sender, e.g. PingBeacon <onboarding@resend.dev>
NEXT_PUBLIC_APP_URL=http://localhost:3000

Keep SUPABASE_SERVICE_ROLE_KEY and CRON_SECRET server-only. The NEXT_PUBLIC_* keys are safe in the browser because Row Level Security is enabled.

4. Run locally

npm run dev
# → http://localhost:3000

Create an account (username becomes your status-page slug), add a monitor, and configure alerts in Settings.

5. Deploy to Vercel

  1. Push the repo to GitHub, then import it at https://vercel.com/new.
  2. Add all variables from .env.local as Environment Variables (Project → Settings → Environment Variables). Mark NEXT_PUBLIC_* as such.
  3. Deploy. Note the production URL, e.g. https://pingbeacon-five.vercel.app.

6. Add GitHub Actions secrets

Open your repo → Settings → Secrets and variables → Actions and add:

Secret Value
PINGBEACON_URL pingbeacon-five.vercel.app (no https://)
CRON_SECRET the same value you used for CRON_SECRET

The workflow runs on schedule automatically and also has a Run workflow button for manual runs.


Alert configuration

  • Slack — create an Incoming Webhook at https://api.slack.com/apps → your app → Incoming Webhooks → copy the https://hooks.slack.com/services/… URL into Dashboard → Settings.
  • Email — create a free Resend key. To send to real addresses you must verify a domain in Resend and set RESEND_FROM_EMAIL to an address on it; without a domain, onboarding@resend.dev can only send to your own account email. Emails are sent on incident open and on recovery.

Billing

PingBeacon has no billing. The paid plans and the Stripe integration were removed when the project went fully open source (see 010_open_source_unlock.sql). There is nothing to configure, nothing to pay for, and no limits to hit — on the hosted instance or self-hosted.


V2 features (Phase 1)

The V2 upgrade adds the "stickiness core" — features that keep users on the platform and make status pages self-promoting.

Status page subscribers (growth loop)

Every public status page has a "Get notified" form. Visitors subscribe with an email, confirm via a Resend email, and then automatically receive incident updates when a monitor goes down and when it recovers. The owner sees the confirmed subscriber count in their dashboard.

  • POST /api/status/[username]/subscribe — create subscriber + send confirmation
  • GET /api/status/[username]/confirm?token=… — confirm a subscription
  • Emails are sent by the check worker on incident open/resolve via Resend

Maintenance windows (churn reducer)

Schedule planned downtime from Dashboard → Maintenance. During a window:

  • Checks still run and are recorded (history stays complete),
  • Incident creation and all alerting (Slack, email, push, subscribers) is suppressed,
  • The public status page shows a yellow "Scheduled maintenance" banner.

Windows are stored in maintenance_windows; the check worker looks up active windows per monitor owner on every cycle.

Weekly digest email

Every Monday 9:00 UTC the cron worker calls POST /api/cron/digest. Each user with a confirmed email alert channel receives a 7-day summary: per-monitor uptime %, average response time, and incident count.

PWA + Web Push notifications

The app is installable as a PWA (/manifest.json, /icon.svg, /sw.js). In Dashboard → Settings → Notifications users can enable browser push; critical incident alerts arrive as persistent notifications that require manual dismiss.

  • Requires VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT (generate with npx web-push generate-vapid-keys)
  • Subscriptions are stored in push_subscriptions; dead endpoints are pruned automatically on send

Phase 1 migration

supabase/migrations/003_v2_phase1.sql adds status_pages, status_page_subscribers, maintenance_windows, and push_subscriptions with RLS. It is idempotent and runs after the two V1 migrations.


V2 features (Phase 2–4) — reliability, advanced checks, teams

Migrations 004_v2_phase2.sql (advanced checks + hardening), 005_v2_phase3.sql (status-page power) and 006_v2_phase4.sql (teams) add the competitive surface:

  • Failure thresholds — incidents open only after N consecutive failures (default 3) and resolve only after M consecutive recoveries (default 2), so a single flaky request never pages anyone. Per-monitor in the edit form.
  • Advanced HTTP checks — expected status codes, keyword contains / not-contains, custom method / headers / body, per-monitor timeout, redirect following.
  • SSL / TLS certificate monitoring — a dedicated check type that flags untrusted chains and certs expiring within a warning window (default 14 days). Certificate expiry is captured on every https check and shown on the monitor page.
  • Per-monitor alert routing — route each Slack/email channel to specific monitors (or leave it global), set quiet hours (UTC) and choose whether a channel gets recovery alerts. Edit in Dashboard → Settings.
  • Incident timeline — owners post updates to an open incident; they render on the public status page and are emailed to status-page subscribers.
  • Status-page branding — brand color, logo URL and custom CSS per page (Settings → Status page), previously dead schema.
  • Subscriber management + unsubscribe — view/remove subscribers in Settings; every subscriber email carries a one-click unsubscribe link.
  • Status API + RSS + embedGET /api/status/[username] returns incidents, branding and maintenance; GET /api/status/[username]/rss is an incidents feed; /embed/[username] is an iframe-able live widget.
  • Custom domains — set a custom domain and verify a _pingbeacon.<domain> TXT record; NEXT_PUBLIC_STATUS_DOMAIN enables a shared status.yourdomain.com/<username> subdomain (rewritten to /status/<username>).
  • Teams / workspaces — create teams, invite members by username, and share monitors. Members see shared monitors read-only; owners/editors can edit. There are no plan limits. Dashboard → Teams.

API

GET /api/status/[username] — public

curl https://pingbeacon-five.vercel.app/api/status/you

Returns monitors with current status, 90-day uptime %, avg response time, per-day uptime bars, any active incidents, and scheduled maintenance windows. No auth required.

POST /api/cron/run-checks — internal

Requires header x-cron-secret: <CRON_SECRET>, returns 401 otherwise. Responds { ok, checked, up, down, alertsSent, errors }.

POST /api/cron/digest — internal

Requires the same x-cron-secret header. Sends the weekly digest email to every user with a confirmed email alert channel. Responds { ok, sent, errors }.

POST /api/status/[username]/subscribe — public

Body { "email": "you@example.com" }. Creates an unconfirmed subscriber and emails a confirmation link. Returns { ok: true }. Idempotent: re-subscribing re-sends the confirmation email.

GET /api/status/[username]/confirm — public

Query param token. Marks the subscriber confirmed. Returns { ok: true }.

POST /api/push/subscribe — auth required

Body is a full PushSubscription JSON (endpoint, keys.p256dh, keys.auth). Saves a Web Push endpoint for the current user. Returns 503 when VAPID keys are not configured.

POST /api/push/unsubscribe — auth required

Body { "endpoint": "..." }. Removes a Web Push endpoint.

GET /api/push/vapid-public-key — public

Returns { ok, publicKey }, or 503 when Web Push is not configured.

GET /api/status/[username]/rss — public

RSS 2.0 feed of the status page's incident history.

POST /api/incidents/[id]/updates — auth required

Body { "message": "..." }. Posts an owner update to an open incident; it is shown on the public timeline and emailed to status-page subscribers + routed alert channels.

GET /api/status/[username]/unsubscribe?token=… — public

One-click unsubscribe for status-page subscribers (uses the confirm token).

POST /api/status/verify-domain — auth required

Body { "domain": "status.example.com" }. Generates a TXT verification token and checks DNS for it; returns the token + verified state.

POST /api/teams/[id]/invite — auth required (team owner)

Body { "username": "teammate" }. Adds a viewer membership to a team.


Troubleshooting

  • Monitors never get checked — confirm the workflow ran (Actions tab) and that PINGBEACON_URL/CRON_SECRET secrets match the server env. Test with the workflow's Run workflow button; the route returns 401 if secrets mismatch.
  • Email alerts don't arrive — Resend free tier can only email your own account until you verify a domain; check the function logs for the Resend status/error body.
  • 401 from the cron routeCRON_SECRET env on Vercel differs from the GitHub secret, or the header name typo'd in a manual curl.
  • GitHub Actions free tier = 5 min checks — that is a GitHub scheduling limit, not a PingBeacon one; set the workflow schedule to * * * * * if your repository allows it.
  • Empty uptime bars / 0% — if a monitor was just created there is no data yet; bars render grey (no data) until the first checks arrive.
  • Push notifications don't arrive — confirm VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT are set and that the site is served over HTTPS (Web Push requires a secure context). Then re-enable push in Settings → Notifications.
  • Subscriber emails don't arrive — the subscribe/confirm/incident emails use Resend; without RESEND_API_KEY they silently skip and log. Verify your domain in Resend to reach arbitrary addresses.
  • Alerts fire during maintenance — the check worker suppresses alerting only when a window in maintenance_windows covers the current time for that monitor's owner; check the window's timezone and that it is saved with a future ends_at.

About

100% free & open-source uptime monitoring — status pages, Slack + email alerts, SLO tracking. Unlimited monitors, no limits. Next.js + Supabase.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages