tutorial

Supabase Backend Monitoring with Vigilmon: Health Checks, Edge Functions & Alerts

Production-grade uptime monitoring for your Supabase backend — monitor the REST API, write custom health check Edge Functions, and get alerted the moment something breaks.

Your Supabase backend went silent. The REST API stopped responding. A Postgres connection pool exhausted itself at 3 AM. Nobody noticed for an hour because there were no external monitors watching.

Supabase gives you a Postgres database, Auth, Storage, Realtime, and a REST API — all managed for you. But "managed" doesn't mean "immune to downtime." Row Level Security misconfigurations, Edge Function cold-start failures, and connection pool exhaustion all happen in production. Vigilmon gives you the independent, external view of your Supabase backend that Supabase's own dashboard cannot provide.

This tutorial shows you how to monitor a Supabase project end-to-end with Vigilmon.

What You'll Build

  • A Supabase Edge Function that acts as a deep health endpoint (DB + Storage + Auth probes)
  • A Vigilmon HTTP monitor targeting your Edge Function
  • A keyword monitor on the Supabase REST API to catch schema errors
  • Alerting channels and a status page

Prerequisites

  • A Supabase project (free tier is fine)
  • Supabase CLI v1.x installed (npm install -g supabase)
  • A free account at vigilmon.online

Step 1: Write a Health Check Edge Function

Supabase Edge Functions run Deno on the edge. They can reach your database, Storage, and any external API — making them the perfect place for a deep health check.

Create the function:

supabase functions new health-check

This creates supabase/functions/health-check/index.ts. Replace its contents:

// supabase/functions/health-check/index.ts
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";

const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
const supabaseKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;

Deno.serve(async (_req) => {
  const checks: Record<string, string> = {};
  let ok = true;

  // 1. Database probe — a lightweight query that exercises the connection pool
  try {
    const client = createClient(supabaseUrl, supabaseKey);
    const { error } = await client.from("_health_probe").select("id").limit(1);
    // A "table not found" error is fine — it means the DB connection is alive
    checks.database =
      !error || error.code === "42P01" ? "ok" : `error: ${error.message}`;
    if (checks.database !== "ok") ok = false;
  } catch (e) {
    checks.database = `exception: ${(e as Error).message}`;
    ok = false;
  }

  // 2. Auth service probe — check the GoTrue endpoint is responding
  try {
    const resp = await fetch(`${supabaseUrl}/auth/v1/health`, {
      headers: { apikey: supabaseKey },
      signal: AbortSignal.timeout(4000),
    });
    checks.auth = resp.ok ? "ok" : `http_${resp.status}`;
    if (!resp.ok) ok = false;
  } catch (e) {
    checks.auth = `exception: ${(e as Error).message}`;
    ok = false;
  }

  // 3. Storage probe — list a known bucket to confirm Storage is reachable
  try {
    const resp = await fetch(`${supabaseUrl}/storage/v1/bucket`, {
      headers: {
        apikey: supabaseKey,
        Authorization: `Bearer ${supabaseKey}`,
      },
      signal: AbortSignal.timeout(4000),
    });
    checks.storage = resp.ok ? "ok" : `http_${resp.status}`;
    if (!resp.ok) ok = false;
  } catch (e) {
    checks.storage = `exception: ${(e as Error).message}`;
    ok = false;
  }

  return Response.json(
    { status: ok ? "ok" : "degraded", checks, ts: new Date().toISOString() },
    { status: ok ? 200 : 503 }
  );
});

Deploy the function:

supabase functions deploy health-check --no-verify-jwt

The --no-verify-jwt flag lets Vigilmon call it without an auth token. If you want the endpoint authenticated, add --no-verify-jwt off and pass X-Health-Token as a custom header in Vigilmon instead (see Step 3).

Your health endpoint is now live at:

https://<project-ref>.supabase.co/functions/v1/health-check

Test it:

curl https://<project-ref>.supabase.co/functions/v1/health-check

Expected response:

{
  "status": "ok",
  "checks": {
    "database": "ok",
    "auth": "ok",
    "storage": "ok"
  },
  "ts": "2026-01-15T10:23:00.000Z"
}

Step 2: Monitor the Health Function with Vigilmon

  1. Log in to Vigilmon and click New Monitor → HTTP.
  2. Set URL to https://<project-ref>.supabase.co/functions/v1/health-check.
  3. Set Check interval to 60 seconds.
  4. Set Expected status code to 200.
  5. Under Advanced → JSON body assertion, add:
    • Path: status
    • Expected value: ok
  6. Save the monitor.

Vigilmon now checks your Supabase health endpoint from multiple external regions. If your Edge Function returns a non-200, times out, or the JSON body says degraded, you'll get an alert.


Step 3: Add a REST API Keyword Monitor

The Supabase PostgREST API exposes your tables directly over HTTP. A common silent failure mode is a permission error or schema cache staleness that returns unexpected error bodies rather than a 5xx. A keyword monitor catches these cases.

Create a second Vigilmon monitor:

  1. Click New Monitor → HTTP.
  2. Set URL to https://<project-ref>.supabase.co/rest/v1/<your-table>?select=id&limit=1.
  3. Add request headers:
    • apikey: your Supabase anon key
    • Authorization: Bearer <anon-key>
  4. Set Expected status code to 200.
  5. Under Advanced → Keyword check, set:
    • Keyword present: (leave blank — just verify the response is not an error JSON)
    • Keyword absent: "code":"PGRST" (PostgREST error prefix)
  6. Save.

This monitor catches PostgREST schema errors (PGRST116, PGRST204, etc.) that can appear as 200-level responses with error bodies rather than proper HTTP error codes.


Step 4: Alert Channels

Go to Notifications → New Channel in Vigilmon and set up:

  • Email — immediate page-level alert to your on-call inbox
  • Webhook — post to Slack, PagerDuty, or Discord

For Slack, create an incoming webhook at api.slack.com/apps and paste the URL into Vigilmon. When Supabase degrades, you'll see:

🔴 DOWN: <project>.supabase.co/functions/v1/health-check
Status: 503 Service Unavailable
Region: EU-West
4 minutes ago

When it recovers:

✅ RECOVERED: <project>.supabase.co/functions/v1/health-check
Downtime: 6 minutes

Step 5: Heartbeat Monitor for Scheduled Jobs

If you use pg_cron or supabase-js in a scheduled server-side job, HTTP monitors won't catch silent failures between runs. The heartbeat pattern fixes this: your job pings Vigilmon at the end of every successful run.

Example for a Supabase Edge Function called on a schedule:

// supabase/functions/nightly-cleanup/index.ts
Deno.serve(async (_req) => {
  try {
    await runCleanup();

    // Ping Vigilmon heartbeat — set VIGILMON_HEARTBEAT_URL in Supabase Secrets
    const heartbeatUrl = Deno.env.get("VIGILMON_HEARTBEAT_URL");
    if (heartbeatUrl) {
      await fetch(heartbeatUrl, { method: "POST" });
    }

    return new Response(JSON.stringify({ ok: true }), { status: 200 });
  } catch (e) {
    // No heartbeat ping on failure — Vigilmon will alert on missed ping
    return new Response(JSON.stringify({ error: (e as Error).message }), {
      status: 500,
    });
  }
});

async function runCleanup() {
  // your cleanup logic here
}

Set the secret:

supabase secrets set VIGILMON_HEARTBEAT_URL=https://vigilmon.online/api/heartbeat/<your-id>

In Vigilmon, create a Heartbeat Monitor and set the grace period to slightly longer than your schedule interval (e.g., 25 hours for a nightly job). Missed pings trigger an alert.


Step 6: Status Page

Go to Status Pages → New Status Page in Vigilmon. Add both monitors (health function and REST API) and share the public URL with your users. Add the badge to your project README:

![Supabase API Status](https://vigilmon.online/badge/<monitor-slug>)

What You've Built

| Scenario | How Vigilmon catches it | |---|---| | Database connection pool exhausted | Health function returns degraded, 503 fires alert | | Auth service (GoTrue) outage | /auth/v1/health probe fails inside health function | | Storage unreachable | Storage probe returns non-200 inside health function | | PostgREST schema cache error | Keyword monitor detects PGRST in response body | | Scheduled Edge Function stops running | Heartbeat monitor misses ping, alert fires | | Edge Function cold-start timeout | HTTP monitor detects timeout, alert fires |


Supabase handles a lot of infrastructure complexity so you don't have to — but you still need an external eye on it. Vigilmon gives you that independent vantage point.

Start monitoring your Supabase backend today — register free at vigilmon.online.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →