Real-time features have become standard. Chat, live dashboards, collaborative editing, presence indicators, multiplayer games, IDE assistants streaming tokens — all of these depend on a long-lived connection that doesn't follow the HTTP request-response pattern. WebSocket is the most common protocol underneath, with Server-Sent Events and long-polling filling specific niches. They're the parts of an application that break in the most user-visible ways when they fail — the cursor stops moving, the chat goes quiet, the dashboard freezes — and the parts that traditional uptime monitoring covers worst.

This post walks through what fails in WebSocket connections, how to monitor them with the tools you already have, and how to wire WebSocket health into an HTTP-based uptime monitor like CronAlert without building a custom monitoring system from scratch.

Why standard HTTP monitoring misses WebSocket failures

A WebSocket connection starts as a normal HTTP/1.1 request with an Upgrade: websocket header. The server responds with HTTP 101 Switching Protocols and the rest of the conversation is framed messages over the same TCP connection. From an HTTP monitor's perspective, the conversation ends at the 101.

Five concrete failure modes that an HTTP-handshake-only check misses:

  • Upgrade succeeds, messages never arrive. The handshake completes but the server's message broker is down. Clients sit on the connection forever, see nothing, and assume the app is broken.
  • Backpressure makes messages slow. The server is healthy but a downstream consumer is slow; messages stack up in the queue and arrive late. P99 message latency climbs from 50ms to 5 seconds without any error.
  • Pings aren't honored. WebSocket has built-in PING/PONG control frames. A server that fails to respond to PINGs causes clients to time out and reconnect repeatedly. The handshake check sees nothing wrong.
  • Connection drops abruptly without a close frame. Common during deploys when connection draining is sloppy. Clients see 1006 Abnormal Closure and reconnect; the deploy looks "successful" because no HTTP requests failed.
  • Subscription state silently desyncs on reconnect. Client thinks it's subscribed to channel X; server doesn't agree (token expired, registry restarted). Messages on channel X stop flowing for that client only.

The pattern is the same one that breaks GraphQL monitoring and gRPC monitoring: the transport layer reports success, the application layer is failing, and naive monitors only watch the transport. The fix is the same: monitor the layer that actually matters.

What to monitor on a WebSocket endpoint

1. The HTTP upgrade handshake

The cheap baseline: hit the WebSocket URL with an HTTP client and the right headers, confirm the response is 101 Switching Protocols. Most uptime tools (CronAlert included) can do this with a standard HTTP monitor:

  • Set the URL to https://ws.example.com/....
  • Set the method to GET and add the headers Upgrade: websocket, Connection: Upgrade, Sec-WebSocket-Version: 13, and Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==.
  • Expect status code 101.

This catches transport-level failures (DNS, TLS, server crash, load balancer down) within one check interval. It misses the application-level failures listed above. Use it as a baseline check, not the only check.

2. A server-side "messages are flowing" heartbeat

The pattern: the WebSocket server itself updates an HTTP endpoint (or a Redis key, or a database row) with a timestamp every time it successfully delivers a message to some client. A small HTTP handler reads that timestamp and returns 200 if it's fresh (less than N seconds old), 503 otherwise.

let lastDeliveryAt = Date.now();

wsServer.on("message-delivered", () => {
  lastDeliveryAt = Date.now();
});

httpServer.get("/healthz/ws", (req, res) => {
  const ageMs = Date.now() - lastDeliveryAt;
  if (ageMs > 60_000) {
    return res.status(503).send(`stale: ${ageMs}ms`);
  }
  res.send(`fresh: ${ageMs}ms`);
});

Then point a CronAlert HTTP monitor at /healthz/ws with keyword monitoring requiring fresh in the body. This catches "the server thinks it's healthy but messages aren't flowing" without needing the monitor itself to speak WebSocket.

The caveat: this heartbeat only fires when there's actual traffic. A low-traffic service can sit at 0 messages for minutes without anything being wrong. Either tune the staleness threshold generously, or have an internal cron job send a synthetic message every minute to keep the heartbeat fresh.

3. A synthetic WebSocket probe

The thorough version: a small worker that opens an actual WebSocket connection, exchanges a known message, and reports the result. The worker can run anywhere — Cloudflare Worker, AWS Lambda, a sidecar, or a long-running container — and it exposes its result over HTTP so an HTTP-only uptime monitor can read it.

// Simplified Cloudflare Worker example
export default {
  async fetch(request, env) {
    const start = Date.now();
    const ws = new WebSocket("wss://ws.example.com/echo");
    const result = await new Promise((resolve) => {
      const timer = setTimeout(() => resolve({ ok: false, reason: "timeout" }), 10_000);
      ws.addEventListener("open", () => ws.send(JSON.stringify({ ping: start })));
      ws.addEventListener("message", (e) => {
        clearTimeout(timer);
        const msg = JSON.parse(e.data);
        resolve({ ok: msg.pong === start, latencyMs: Date.now() - start });
        ws.close();
      });
      ws.addEventListener("error", () => resolve({ ok: false, reason: "error" }));
    });
    return Response.json(result, { status: result.ok ? 200 : 503 });
  }
};

The server needs an /echo endpoint (or whatever you call it) that echoes a known message back; this can be a tiny dedicated channel rather than touching production message routing. Point a CronAlert HTTP monitor at the worker URL. The monitor sees the probe result as a simple 200/503; the heavy lifting happens out of band.

The synthetic probe catches all five failure modes above: handshake works, message exchange works, pings work, no abrupt close, subscription path was honored. It also produces a latency number you can alert on. Run it less often than the handshake check — once every five minutes is usually enough — because opening real WebSocket connections is more expensive than HTTP probes.

4. Per-connection message latency

For services where end-to-end latency is the product (collaborative editing, multiplayer games), measure the round-trip time for a known message and alert on percentile degradation. P50 holding steady while P99 grows from 50ms to 2 seconds is a signal that something downstream is degrading even if no errors are firing. Most application telemetry systems can compute this; surface a P99 stat on the same /healthz/ws endpoint and alert on threshold crossings via keyword monitoring against the response body.

5. Connection churn during deploys

Long-lived connections magnify deploy hiccups. A 30-second deploy that drops every WebSocket connection forces every client to reconnect, every authenticated subscription to be re-established, and every server-side cache to be re-populated. Monitor the per-deploy reconnect rate and alert if it exceeds a baseline; sustained elevated reconnects after a deploy suggests draining was incomplete.

Server-Sent Events and long-polling

The same monitoring patterns apply to other real-time transports with minor adjustments:

  • Server-Sent Events (SSE) are plain HTTP responses with Content-Type: text/event-stream. An HTTP monitor that supports streaming responses can read the stream and assert that an event arrives within an expected window. The "messages are flowing" heartbeat pattern works identically — surface a /healthz/sse endpoint with the freshness of the last event sent.
  • Long-polling endpoints are ordinary HTTP requests with a high timeout. Monitor them like any other endpoint, but set the timeout generously (e.g., 60 seconds) and alert on hard failures rather than slow responses — slow is normal for long-polling.
  • WebTransport / QUIC-based real-time is still niche but follows the same pattern: handshake check at the transport layer, synthetic probe at the application layer, server-side "messages are flowing" heartbeat exposed over HTTP for an HTTP monitor to read.

WebSocket-specific failure modes

A handful of failures show up often enough to deserve their own runbook entries:

  • Cloudflare's 100-second idle timeout. Connections through Cloudflare are killed after 100 seconds of no traffic. Apps that rely on long-idle WebSocket connections without ping/pong keep-alives see periodic mysterious disconnects. The fix is to enable PING/PONG keepalives on the server side, not to debug the network.
  • Load balancer connection limits. Many cloud load balancers cap the per-instance connection count. WebSocket connections are long-lived; an instance fills up faster than it would with HTTP-only traffic. Monitor connection counts per instance and alert before the cap.
  • Sticky session requirements. Some WebSocket frameworks (older Socket.IO setups, for example) require sticky sessions on the load balancer. A configuration change that removes stickiness silently breaks the protocol because the handshake and subsequent frames hit different backends.
  • TLS termination and ALPN. Like gRPC, WebSocket over TLS depends on TLS terminating with the right ALPN negotiation. A misconfigured TLS inspection appliance or proxy can succeed at TLS but break the upgrade.
  • Subprotocol negotiation. The Sec-WebSocket-Protocol header is part of the handshake. Clients and servers that disagree on subprotocols disconnect after the handshake. Surface the negotiated subprotocol in your probe to catch this.

Wiring WebSocket monitoring into CronAlert

A complete monitoring setup for a WebSocket service typically combines three monitors:

  1. HTTP handshake monitor on the WebSocket URL with custom headers, expecting 101. Runs at 1-minute intervals from multiple regions.
  2. Heartbeat endpoint monitor on the server-exposed /healthz/ws. Keyword-monitored for fresh. Runs at 1-minute intervals.
  3. Synthetic probe wrapper on a tiny Cloudflare Worker that opens a real WebSocket. Runs at 5-minute intervals. Surfaces latency in the response body for percentile tracking.

The three checks together cover transport, server-side application health, and end-to-end synthetic flow. Each one runs as an ordinary HTTP monitor; CronAlert doesn't need to speak WebSocket natively because the WebSocket work is moved into the worker. The health check endpoint guidance applies: keep them shallow, fast, no side effects, no auth.

Alerting strategy for WebSocket monitoring

WebSocket alerts deserve their own routing because the user-visible impact is more immediate than for HTTP request failures:

  • Handshake failure from multiple regions pages immediately — the service is down for all users.
  • Stale heartbeat (no messages flowing) pages immediately — the service appears up but is silently broken.
  • Synthetic probe failure pages on consecutive failures (3+ in a row) to avoid one-off flakes. Synthetic probes are inherently flakier than HTTP probes because more moving parts are involved.
  • Sustained P99 message latency degradation opens a non-paging alert on a chat channel. Lets the team get ahead of the incident before it becomes an outage.
  • Elevated reconnect rate after deploys goes to a chat channel. Often a one-time deploy artifact; not worth waking on-call.

The general alert fatigue principles apply — consecutive-check verification, multi-region quorum, and tiered routing matter more for WebSocket monitoring than for HTTP because synthetic probes are inherently flakier and false positives are easier to generate.

Frequently asked questions

Can I monitor a WebSocket endpoint with a normal HTTP uptime monitor?

Partially. An HTTP monitor with the right Upgrade headers can verify the handshake (HTTP 101) but can't verify message flow. Pair it with a server-exposed heartbeat endpoint and a synthetic-probe worker for full coverage.

What can fail in a WebSocket connection that HTTP monitoring misses?

Handshake succeeds but messages never arrive; messages arrive but slowly; PINGs aren't honored; connection drops without a close frame; subscription state silently desyncs on reconnect.

How do I monitor WebSockets if my uptime tool only speaks HTTP?

Expose a server-side /healthz/ws that reflects whether messages are flowing, and/or run a small Cloudflare Worker (or Lambda) that opens an actual WebSocket and exposes the result over HTTP. CronAlert HTTP monitors then read the result.

Should I monitor Server-Sent Events and long-polling endpoints the same way?

Yes — the patterns translate. Handshake check, server-side freshness heartbeat, and a synthetic probe that exchanges a real message all apply.

How often should WebSocket synthetic probes run?

Five-minute intervals are usually fine. Real-connection probes are more expensive than HTTP probes; pair them with a fast HTTP handshake check at 1-minute intervals for the cheap-and-fast layer.

Add WebSocket monitoring to your uptime stack

WebSocket monitoring doesn't require a separate product — it's three HTTP monitors pointed at the right surfaces. Add a handshake check, a server-exposed heartbeat, and a synthetic-probe wrapper, and you'll catch the failure modes that simpler monitoring misses. Create a free CronAlert account and start with the handshake check; layer in the heartbeat and probe as the setup matures.

Related reading: gRPC endpoint monitoring, GraphQL endpoint monitoring, API endpoint monitoring, HTTP health check endpoints, and keyword monitoring.