Bun's pitch is speed — faster startup, faster installs, a server in five lines with Bun.serve. And it delivers, which is exactly why monitoring conversations about Bun tend to be short and wrong: "it's fast and it hasn't crashed yet" describes every process the day before its first incident. From the outside, fast-and-down and slow-and-down are indistinguishable.

The honest risk profile of a Bun app in production is simple to state: it's usually one process. One very fast process serving HTTP, holding WebSocket connections, and often running scheduled work on timers — on a runtime young enough that ecosystem edge cases still surface. None of that is a reason not to ship Bun (we're fans). It's a reason to monitor the specific ways one-process apps fail. This guide covers Bun.serve, Elysia, and Hono apps, and joins our companion guides for Express/Node.js and Go.

Write a real /healthz — it's ten lines

Process-up is not app-up. A health endpoint should prove the dependencies work — a trivial database query, a cache round-trip — and name the failure when they don't:

import { Database } from "bun:sqlite";

const db = new Database("app.db");

Bun.serve({
  port: 3000,
  routes: {
    "/healthz": () => {
      try {
        db.query("SELECT 1").get();
        return Response.json({ ok: true });
      } catch (err) {
        return Response.json(
          { ok: false, dependency: "database" },
          { status: 503 },
        );
      }
    },
    // ...your app routes
  },
});

The same shape in Hono is app.get("/healthz", ...), and in Elysia .get("/healthz", ...) — the framework doesn't matter, the dependency check does. Swap the bun:sqlite query for your Postgres client's SELECT 1 as appropriate, keep the whole thing under a second, and don't cache it. Our guides to health check endpoints and database health checks cover the traps (timeouts, auth, accidentally DDoSing your own DB) in depth. Then point an external monitor at it — an unpolled health endpoint is a dashboard, not an alarm.

The one-process problem

Node teams inherited a decade of process-management habits — PM2, the cluster module, one worker per core. Bun deployments, in our experience, often skip all of that: one bun run server.ts in a container or under nothing at all. Two consequences:

  • A crash is a total outage. Exceptions thrown inside the request handler are caught by the server and become error responses — those won't kill you. But a crash outside the request path (a timer callback, a failed startup after deploy, an OOM kill) takes down the only process, and HTTP, WebSockets, and scheduled work all stop together. Run Bun under a supervisor — systemd with Restart=always or a Docker restart policy (our Docker and self-hosted guide has configs) — and keep an external monitor on it for the failure the supervisor can't fix: the crash-loop, where the process restarts, dies on the same bad config, and repeats forever while "the service is running" stays technically true in between.
  • Restarts drop everything in flight. With no sibling processes, every deploy and every supervisor restart severs all live connections at once. Quick HTTP requests retry invisibly; WebSocket-heavy apps feel it. If you scale out (Bun supports SO_REUSEPORT-style multi-process setups on Linux, or you run replicas behind a proxy), your monitoring should watch the proxy's public face and at least one path that exercises a real backend process.

The idle-timeout gotcha: slow endpoints fail, fast ones lie

Bun.serve closes connections that go idle — around 10 seconds by default as of Bun 1.x, configurable via the idleTimeout option (in seconds, up to 255). The subtlety is what "idle" means: a handler that's computing hard but sending nothing looks idle to the socket. So the report generator that takes 15 quiet seconds gets its connection dropped mid-request, while every quick page stays green — and your uptime dashboard says everything's fine because you only monitored the homepage.

Three fixes, in order of preference: move genuinely slow work to a background job and return a job ID; stream partial output (progress events, chunked responses) so the connection never looks idle; or raise idleTimeout deliberately for the server hosting the slow routes. Whichever you choose, add a monitor for your slowest real endpoint, not just your fastest — and set its timeout and response-time thresholds to match what that route actually needs.

Bun-specific failure modes worth a monitor

  • The event loop is still single-threaded. Bun being fast doesn't repeal the rule: synchronous CPU work (parsing a huge JSON payload, a hot regex, sync crypto) blocks every request in the process. This shows up as response-time degradation before it becomes timeouts — a response-time threshold on your monitors catches the drift early.
  • Node-compat edge cases arrive with dependencies, not with your code. Bun's Node.js compatibility is broad, but the gaps that remain tend to surface deep inside some transitive dependency — often only after a routine bun update, and sometimes only on the code path that runs nightly. The defense is boring: monitors on the endpoints that exercise your heaviest dependencies, and heartbeats on scheduled work, so a compat regression fails loudly instead of quietly.
  • WebSockets are a second plane. Bun.serve's first-class WebSocket support means many Bun apps are really two apps: an HTTP plane your monitor sees and a WebSocket plane it can't. A 200 from the homepage proves nothing about whether sockets are connecting. Expose a tiny stats endpoint (active connection count, last-message timestamp) and put a keyword monitor on it — the same pattern we use for WebSocket monitoring generally.
  • In-process schedulers die with the process. No built-in cron in Bun means scheduled work is croner/node-cron on a timer inside the server, or system cron outside it. The in-process kind shares the server's fate — and after the supervisor restarts the process, the schedule restarts from zero with no record that runs were missed. Give every scheduled job a heartbeat: ping https://cronalert.com/api/heartbeat/<token> after each successful run, and silence becomes an alert. The full pattern — including the Redis-backed variant — is in our node-cron and BullMQ guide, which applies to Bun unchanged.

A concrete CronAlert setup

Here's the five-monitor starter set for a typical Bun app, after you create a free CronAlert account:

  • Homepage or app shell — HTTP monitor, expect 200. The basic "is it up."
  • /healthz — HTTP monitor on the dependency-aware endpoint above; this is the one that catches the database being down while the process hums along.
  • Slowest real endpoint — HTTP monitor with thresholds tuned to that route, for the idle-timeout and event-loop-blocking classes of failure.
  • SSL certificate — included with HTTP monitors; expiry warnings before your users see browser errors.
  • Heartbeats for scheduled work (Pro) — one per job that matters, expected interval matching the schedule plus grace.

The first four fit in CronAlert's free plan (25 monitors, 3-minute intervals, email/Slack/Discord/webhook alerts). Pro ($5/mo) tightens checks to 1 minute and adds the heartbeats, keyword checks for the WebSocket stats endpoint, and the full write API — which pairs nicely with Bun projects, since you can script monitor creation in a bun run deploy step or manage it from Claude Code via CronAlert's MCP server.

Frequently asked questions

How do I add a health check to a Bun app?

A /healthz route that runs a trivial DB query and returns 200/503 accordingly — ten lines in Bun.serve, one route in Elysia or Hono. Then point an external monitor at it.

What happens when the Bun process crashes?

HTTP, WebSockets, and in-process schedulers all stop at once — it's usually the only process. Supervisor for restarts, external monitor for crash-loops.

Why do only my slow endpoints fail?

Likely the ~10-second default idle timeout on Bun.serve cutting silently-computing handlers. Background-job the work, stream output, or raise idleTimeout — and monitor the slow route directly.

How do I monitor scheduled jobs in Bun?

Heartbeats. In-process schedulers die with the process without throwing anything; a ping-after-success plus a missed-beat alert is the only reliable detection.

Fast is not the same as up

Bun earns its speed reputation, but speed is a property of the requests that complete. The failure modes that matter — the crashed single process, the stuck crash-loop, the silently dead scheduler, the slow endpoint hitting an idle timeout — are all invisible from inside the app and obvious from outside it. A dependency-aware /healthz, four HTTP monitors, and a heartbeat per scheduled job cover the lot. Create a free CronAlert account and have the first monitor checking before your next bun run deploy finishes.

Related reading: Uptime monitoring for Express and Node.js, Go applications, node-cron and BullMQ scheduled jobs, health check endpoints that don't lie, and WebSocket monitoring.