Cloudflare Pages hosts everything from static documentation sites to full-stack applications with server-side Pages Functions. Both categories can fail silently: a broken build deploys successfully, a Pages Function hits a CPU limit, or a connected D1 database returns errors that look fine from Cloudflare's own dashboard. Vigilmon catches these failures from the outside — an independent probe that doesn't trust Cloudflare's internal signals.
If you're looking for monitoring specifically for Cloudflare Workers (standalone, non-Pages deployments), see our Cloudflare Workers monitoring tutorial. This article focuses on the Pages platform and its Pages Functions, which have different deployment semantics, routing, and KV/D1 binding patterns.
What You'll Build
- A
/healthPages Function that probes real dependencies (D1, KV, external APIs) - A Vigilmon HTTP monitor targeting your Pages deployment
- Keyword monitoring to catch broken deployments that return 200 with error content
- Alert channels and a public status page
Prerequisites
- A Cloudflare Pages project
- Wrangler v3+ CLI (
npm install -g wrangler) - A free account at vigilmon.online
Cloudflare Pages vs. Workers — What's Different
Before writing the health check, it's worth understanding how Pages Functions differ from Workers:
| Aspect | Cloudflare Workers | Cloudflare Pages Functions |
|---|---|---|
| Entry point | export default { fetch } | File-based routing in /functions |
| Deployment | wrangler deploy | Git push or wrangler pages deploy |
| Bindings | wrangler.toml | wrangler.toml or Pages dashboard |
| Route control | Full — any path | Automatic — mirrors /functions file structure |
| Static assets | Not included | Served alongside from /public or framework output |
Pages Functions coexist with your static assets. A file at functions/health.ts creates a serverless handler at the /health route of your Pages site.
Step 1: Create a Health Check Pages Function
Create functions/health.ts in your Pages project:
// functions/health.ts
interface Env {
DB: D1Database; // D1 binding (if used)
KV_STORE: KVNamespace; // KV binding (if used)
}
interface CheckResult {
status: "ok" | "error";
detail?: string;
}
export const onRequest: PagesFunction<Env> = async (context) => {
const checks: Record<string, CheckResult> = {};
let ok = true;
// 1. D1 database probe
if (context.env.DB) {
try {
const result = await context.env.DB.prepare("SELECT 1 AS alive").first<{
alive: number;
}>();
checks.d1 = result?.alive === 1
? { status: "ok" }
: { status: "error", detail: "unexpected query result" };
if (checks.d1.status !== "ok") ok = false;
} catch (e) {
checks.d1 = { status: "error", detail: (e as Error).message };
ok = false;
}
}
// 2. KV namespace probe
if (context.env.KV_STORE) {
try {
const probeKey = "__health_probe__";
await context.env.KV_STORE.put(probeKey, "1", { expirationTtl: 60 });
const val = await context.env.KV_STORE.get(probeKey);
checks.kv = val === "1"
? { status: "ok" }
: { status: "error", detail: "read-after-write mismatch" };
if (checks.kv.status !== "ok") ok = false;
} catch (e) {
checks.kv = { status: "error", detail: (e as Error).message };
ok = false;
}
}
// 3. External dependency probe (replace with your real upstream)
try {
const resp = await fetch("https://api.example.com/ping", {
signal: AbortSignal.timeout(3000),
});
checks.upstream = resp.ok
? { status: "ok" }
: { status: "error", detail: `http_${resp.status}` };
if (!resp.ok) ok = false;
} catch (e) {
checks.upstream = { status: "error", detail: (e as Error).message };
ok = false;
}
return Response.json(
{
status: ok ? "ok" : "degraded",
checks,
ts: new Date().toISOString(),
},
{ status: ok ? 200 : 503 }
);
};
Bindings configuration
Add D1 and KV bindings to wrangler.toml:
[[d1_databases]]
binding = "DB"
database_name = "your-database"
database_id = "your-d1-database-id"
[[kv_namespaces]]
binding = "KV_STORE"
id = "your-kv-namespace-id"
Deploy:
wrangler pages deploy ./dist --project-name=your-pages-project
Or push to your connected Git branch and let Cloudflare's CI build it. The function is available at:
https://your-pages-project.pages.dev/health
Test it:
curl https://your-pages-project.pages.dev/health
Step 2: Vigilmon HTTP Monitor
- Log in to Vigilmon and click New Monitor → HTTP.
- Set URL to
https://your-pages-project.pages.dev/health. - Set Check interval to 60 seconds.
- Set Expected status code to
200. - Under Advanced → JSON body assertion:
- Path:
status - Expected value:
ok
- Path:
- Save.
Vigilmon monitors your Pages health endpoint from multiple external regions. A broken D1 binding, a cold-start timeout, or a 503 from Cloudflare itself all trigger an alert.
Step 3: Keyword Monitor for the Static Site
If your Pages project serves a static site alongside Functions, broken builds can deploy successfully and return 200 with broken content — empty HTML, missing JS bundles, or a "build failed" placeholder page. A keyword monitor catches this.
Create a second Vigilmon monitor:
- Click New Monitor → HTTP.
- Set URL to
https://your-pages-project.pages.dev/. - Set Expected status code to
200. - Under Advanced → Keyword check:
- Keyword present: a unique string from your site's actual HTML (e.g., your site name or a specific
<title>substring) - Keyword absent:
"Build failed"or"Error"(adjust to your framework's error page text)
- Keyword present: a unique string from your site's actual HTML (e.g., your site name or a specific
- Save.
This monitor catches broken deployments that Cloudflare accepted and served but that rendered broken content.
Step 4: Preview Deployment Monitoring
Cloudflare Pages creates a unique URL for every Git branch deployment:
https://<branch-name>.<project>.pages.dev/health
For staging or release branches, add a second monitor pointing at the branch URL. This catches regressions in your staging environment before they reach production.
Step 5: Alert Channels
In Vigilmon, go to Notifications → New Channel:
- Email — immediate on-call alert
- Slack webhook — team channel notification
Configure the Slack webhook at api.slack.com/apps and paste the URL into Vigilmon. When your Pages site degrades:
🔴 DOWN: your-pages-project.pages.dev/health
Status: 503 Service Unavailable
Region: EU-West
3 minutes ago
When it recovers:
✅ RECOVERED: your-pages-project.pages.dev/health
Downtime: 5 minutes
Step 6: Protecting the Health Endpoint
Pages Functions run in Cloudflare's zero-trust environment. If your site requires authentication, add a token check to the health function:
// functions/health.ts (with token guard)
export const onRequest: PagesFunction<Env> = async (context) => {
const token = context.request.headers.get("x-health-token");
if (token !== context.env.HEALTH_TOKEN) {
return new Response(JSON.stringify({ error: "unauthorized" }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
}
// ... health check logic
};
Set HEALTH_TOKEN as a Pages secret in the Cloudflare dashboard (Settings → Environment variables). In Vigilmon, add a Custom header to the monitor: x-health-token: <your-secret>.
Step 7: Status Page
Go to Status Pages → New Status Page in Vigilmon. Add both monitors (health function + homepage keyword check) and share the public URL. Embed the badge in your README or docs:

What You've Built
| Scenario | How Vigilmon catches it |
|---|---|
| Pages Function CPU/timeout error | HTTP monitor sees 503, fires alert |
| D1 database connection failure | Health function returns degraded |
| KV namespace misconfiguration | KV probe fails inside health function |
| Broken static deployment | Keyword monitor detects missing page content |
| Cloudflare edge outage | HTTP monitor sees timeout from external probe |
| Preview branch regression | Branch-specific monitor catches pre-production failures |
Cloudflare Pages deploys fast and globally — but that speed means broken deployments go live just as fast. External monitoring from Vigilmon gives you the independent vantage point that catches what Cloudflare's own dashboard doesn't.
Start monitoring your Cloudflare Pages site today — register free at vigilmon.online.