How to Monitor Hono with Vigilmon
Hono is fast by design — sub-millisecond routing, tiny bundle, runs everywhere. But "everywhere" is also the challenge: Cloudflare Workers, Bun, Deno, AWS Lambda, Node.js all have different failure modes, cold start behaviors, and observability surfaces.
This tutorial shows you how to make a Hono application fully observable regardless of where it runs. By the end you'll have:
- A
/healthendpoint that works on any Hono runtime - HTTP uptime monitoring with Vigilmon
- Latency alerting with a response-body canary
- Heartbeat monitoring for Hono scheduled Workers
- Slack alerts for outages and recoveries
Why Monitor Hono Applications
Hono's minimal footprint means fewer moving parts — but the platform underneath can still fail:
- Cloudflare Workers — CPU time limits (10ms–50ms), memory limits, subrequest limits; silent 1101 or 1042 errors from Cloudflare's edge
- Bun runtime — process crash on uncaught promise rejection, port bind failure on restart
- Deno Deploy — region-specific rollout failures; cold starts on low-traffic routes
- AWS Lambda — function throttle, timeout (default 3s), cold start latency spikes
- Node.js — OOM crashes, SIGKILL on container restart, uncaught exceptions
Hono surfaces all of these as HTTP errors or timeouts. A monitoring tool that pings your endpoint from multiple regions will catch every one of them.
Key Metrics to Watch
| Metric | What it means |
|--------|---------------|
| HTTP status of /health | App process is alive and responding |
| Response time p50/p95 | Cold starts, upstream latency, CPU pressure |
| Error rate on key routes | Unhandled exceptions reaching users |
| Scheduled Worker execution | Cron triggers fired, job completed successfully |
| KV / D1 / R2 reachability | Storage dependencies healthy |
Step 1: Add a Health Endpoint
Hono's API is runtime-agnostic, so the health route looks identical whether you deploy to Cloudflare Workers, Bun, or Node.js:
// src/routes/health.ts
import { Hono } from 'hono';
const health = new Hono();
health.get('/', (c) => {
return c.json({
status: 'ok',
runtime: c.env?.CF_WORKER_ENV ?? process.env.NODE_ENV ?? 'unknown',
timestamp: new Date().toISOString(),
});
});
export { health };
Mount it in your main app:
// src/index.ts
import { Hono } from 'hono';
import { health } from './routes/health';
const app = new Hono();
app.route('/health', health);
export default app;
Adding a Dependency Check
If your Hono app reads from Cloudflare KV, D1, or an external API, check those too:
// src/routes/health.ts — Cloudflare Workers with D1 and KV
import { Hono } from 'hono';
type Bindings = {
DB: D1Database;
KV: KVNamespace;
};
const health = new Hono<{ Bindings: Bindings }>();
health.get('/', async (c) => {
const checks: Record<string, 'ok' | 'error'> = {};
let allOk = true;
// D1 check
try {
await c.env.DB.prepare('SELECT 1').run();
checks.d1 = 'ok';
} catch {
checks.d1 = 'error';
allOk = false;
}
// KV check
try {
await c.env.KV.get('__health_probe__');
checks.kv = 'ok';
} catch {
checks.kv = 'error';
allOk = false;
}
return c.json({ status: allOk ? 'ok' : 'degraded', checks }, allOk ? 200 : 503);
});
export { health };
Test locally:
# Bun
bun run src/index.ts &
curl http://localhost:3000/health
# Wrangler (Cloudflare)
wrangler dev
curl http://localhost:8787/health
Step 2: Set Up HTTP Monitoring in Vigilmon
- Sign up at vigilmon.online (free, no card required)
- Click New Monitor → HTTP
- Enter
https://your-worker.workers.dev/health(or your custom domain) - Set check interval: 1 minute (paid) or 5 minutes (free)
- Expected status code:
200 - Save
For multi-region Hono deployments (Cloudflare's global network), Vigilmon checks from multiple probe regions independently, so a regional edge failure is distinguishable from a full outage.
Add monitors for your most critical routes alongside the health check:
https://your-worker.workers.dev/health → process alive
https://your-worker.workers.dev/api/ping → API routing works
Step 3: Latency Canary for Cold Start Detection
Hono on Cloudflare Workers starts cold when a Worker isolate is spun up fresh. Cold starts are usually <5ms for Hono itself but can spike with heavy initialization.
Add a latency canary endpoint:
health.get('/latency', async (c) => {
const THRESHOLD_MS = 100;
const start = performance.now();
// Run a lightweight operation representative of real work
await Promise.resolve(); // replace with a real DB ping
const elapsed = Math.round(performance.now() - start);
if (elapsed > THRESHOLD_MS) {
return c.json({ slow: true, latencyMs: elapsed }, 503);
}
return c.json({ slow: false, latencyMs: elapsed });
});
Create a Vigilmon monitor for /health/latency expecting status 200. When cold starts or upstream slowness pushes latency above your threshold, you're alerted before SLA is breached.
Step 4: Heartbeat Monitoring for Scheduled Workers
Cloudflare Workers supports cron triggers via scheduled in wrangler.toml:
# wrangler.toml
[[triggers.crons]]
crons = ["0 * * * *"] # every hour
Your Worker handles it:
// src/index.ts
export default {
async fetch(request: Request, env: Env): Promise<Response> {
return app.fetch(request, env);
},
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
ctx.waitUntil(runHourlyJob(env));
},
};
async function runHourlyJob(env: Env): Promise<void> {
try {
// your job logic here
await processQueue(env);
// Ping heartbeat on success
if (env.VIGILMON_JOB_HEARTBEAT) {
await fetch(env.VIGILMON_JOB_HEARTBEAT);
}
} catch (err) {
console.error('Hourly job failed:', err);
// No ping → Vigilmon alerts on missed heartbeat
}
}
In wrangler.toml, add the secret binding:
[vars]
# set via wrangler secret put VIGILMON_JOB_HEARTBEAT
In Vigilmon:
- Click New Monitor → Heartbeat
- Set expected interval: 2 hours (generous buffer for the 1-hour cron)
- Copy the ping URL
- Run
wrangler secret put VIGILMON_JOB_HEARTBEATand paste it
If the cron trigger fires but the job throws, or if Cloudflare's cron scheduler misses an invocation, you get an alert.
Step 5: Slack Alerts
Go to Notifications → New Channel → Slack in Vigilmon and paste your incoming webhook URL. Enable it on your monitors.
When your Worker goes down:
🔴 DOWN: your-worker.workers.dev/health
Status: 503 Service Unavailable
Region: NA-East
1 minute ago
On recovery:
✅ RECOVERED: your-worker.workers.dev/health
Downtime: 4 minutes
Step 6: Error Handling Middleware
Add Hono error middleware to ensure unexpected exceptions return a proper 500 (not an empty response, which some monitoring tools can't distinguish from a timeout):
// src/index.ts
import { Hono } from 'hono';
const app = new Hono();
app.onError((err, c) => {
console.error('Unhandled error:', err);
return c.json({ error: 'Internal server error' }, 500);
});
app.notFound((c) => {
return c.json({ error: 'Not found' }, 404);
});
// routes...
export default app;
Vigilmon treats 500 as a failure by default (you can configure expected status). With explicit 500 responses, downtime attribution is accurate.
What You've Built
| What | How |
|------|-----|
| Health endpoint | Hono route returning 200/503 based on dependency state |
| HTTP uptime monitoring | Vigilmon HTTP monitor → /health |
| Latency canary | /health/latency returns 503 on slow response |
| Cron job monitoring | Heartbeat ping in scheduled() handler |
| Slack alerts | Vigilmon Slack notification channel |
| Error middleware | Explicit 500 on unhandled exceptions |
Your Hono app is now fully observable across any runtime. You'll know about outages and slowdowns before users report them.
Next Steps
- Add a
/health/upstreamcheck that verifies third-party APIs your Hono app depends on - Monitor response time trends in Vigilmon — gradual slowdowns on a single region often precede wider failures
- For Node.js deployments, add a process manager (PM2, systemd) restart heartbeat alongside the app heartbeat
Get started free at vigilmon.online.