Workers WebSockets now reply to Close frames on their own!
Hey everyone, it's Shiichan! Today I found a small but handy change around WebSockets, so let me walk you through it.
Cloudflare ChangelogWhat was announced?
From Cloudflare's Changelog: the Workers runtime now automatically sends a reciprocal Close frame when it receives a Close frame from the peer. The readyState transitions to CLOSED before the close event fires, matching the WebSocket specification and standard browser behavior.
The story so far
Until now, when your Worker received a Close frame, you often had to call close() yourself for the connection to shut down the way the spec describes. The "receive one, send one back" handshake was something you had to write by hand.
What changes
Existing code that calls close() inside the close event handler keeps working, so no worries. A close() call on an already-closed WebSocket is silently ignored.
In other words, the runtime now handles the "send the reply Close" cleanup for you. By the time the event arrives, readyState is already CLOSED, so you no longer need to write that close() call.
Dive Deep
This behavior is on by default for Workers whose compatibility date is on or after 2026-04-07 (via the web_socket_auto_reply_to_close compatibility flag).
That said, if your Worker sits between a client and a backend as a WebSocket proxy, you may want to coordinate the close on each side at your own pace. In that case, pass { allowHalfOpen: true } to accept(). Then readyState stays CLOSING inside the close event, and you can call server.close() whenever you're ready.
const [client, server] = Object.values(new WebSocketPair());
server.accept({ allowHalfOpen: true });
server.addEventListener("close", (event) => {
// readyState is still CLOSING here.
server.close(event.code, "done");
});
If you want more, read WebSockets Close behavior.
Wrap-up
- Workers now auto-replies to Close frames (matching the spec and browsers)
- On by default for compatibility date
2026-04-07and later; manualclose()calls are ignored, so existing code stays safe - For proxy use cases,
accept({ allowHalfOpen: true })keeps the manual coordination you had before - A quiet but welcome update for Workers devs who lean hard on WebSockets!