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
- Log in to Vigilmon and click New Monitor → HTTP.
- Set URL to
https://<project-ref>.supabase.co/functions/v1/health-check. - Set Check interval to 60 seconds.
- Set Expected status code to
200. - Under Advanced → JSON body assertion, add:
- Path:
status - Expected value:
ok
- Path:
- 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:
- Click New Monitor → HTTP.
- Set URL to
https://<project-ref>.supabase.co/rest/v1/<your-table>?select=id&limit=1. - Add request headers:
apikey: your Supabase anon keyAuthorization:Bearer <anon-key>
- Set Expected status code to
200. - 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)
- 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:

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.