workerd: WebSocket connection failed before handshake completed
Fixes workerd WebSocket connections failing before the handshake completes. Use it when a worker-side or proxied WebSocket upgrade never finishes. The key trigger is the pre-handshake failure; the fix is verifying the server returns a 101 with the right upgrade headers on the right route.
TL;DR
"Failed before handshake completed" means the WebSocket upgrade never got its 101 response, so debug the handshake, not the socket. Confirm the server actually speaks WebSocket on that route, that the upgrade request carries the right headers, and that nothing in the middle (middleware, proxy, wrong route) eats the upgrade. Fix the handshake and the connection follows.
WebSocket connection failed before handshake completedSteps
- Test the endpoint with a standalone WebSocket client from your dev machine, bypassing the worker entirely.
Expected: the connection establishes. If it fails here too, the server side is the problem, not the worker.
- Inspect the upgrade response. The server must answer the upgrade request with status 101 and the WebSocket accept headers. Anything else (404, 500, a plain 200) means the handshake never started.
Expected: a 101 response on the exact path the worker connects to.
- Check that the upgrade headers survive the trip. If the worker proxies the upgrade, make sure middleware forwards the Upgrade and Connection headers untouched, and that the target route matches exactly (a missing trailing path segment 404s the upgrade).
Expected: headers arrive intact at the WebSocket server.
- In local dev, confirm the target WebSocket server is actually running and listening for upgrades on your dev machine, and that the worker points at the right host and port.
Expected: wrangler dev plus the standalone client from step 1 both connect.
- For Durable Object WebSockets, confirm the request reaches the Durable Object's fetch handler and that the object accepts the socket (the standard accept call in the constructor or fetch path).
Expected: the DO upgrades the connection and the client handshake completes.
Use this when
- A worker's outbound WebSocket fails before the handshake completes
- A worker proxying a WebSocket upgrade sees the handshake die
- Durable Object WebSocket connections fail at connect time
Not for this skill when
- The connection establishes and then drops; look at idle timeouts and heartbeats instead
- The failure is a 403 from the server; that is auth, not a handshake bug
- The client is a browser with its own networking issues rather than the worker
Variant phrasings
- websocket handshake failed cloudflare worker
- durable object websocket upgrade failed
- workerd client websocket connection refused before handshake
Why it happens
A WebSocket connection is just an HTTP request that asks to upgrade, and workerd only completes it on a clean 101 answer. The usual breakages are mundane: the route does not exist on the server, a middleware strips the upgrade headers, or the dev server is not listening. Because the error fires before any WebSocket frames exist, socket-level debugging tools show nothing useful.
Edge cases
- The ws and wss schemes matter; mixing them (worker on wss talking to a ws dev server) fails the handshake.
- Some proxies buffer or modify upgrade requests; test direct before blaming the worker.
- Hibernatable Durable Object WebSockets must be accepted through the DO API; a plain Response return from the DO fetch handler never upgrades.
- A server that rate-limits or bot-challenges the upgrade path returns a non-101 page that looks like a handshake bug from the client side.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_rkv89BlPVEyEAnss5BQlLg
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.