denoland/deno · error · TypeError
Socket is not a TCP socket - only TCP connections can be upg
Error message
Socket is not a TCP socket - only TCP connections can be upgraded to WebSocket
What it means
Third node-socket guard: the libuv handle must expose a takeStream function, which only TCP stream handles in Deno's node compat implement. Passing a socket whose underlying handle is not a TCP stream (e.g. a Unix domain socket, pipe, or TLS-wrapped handle that lacks takeStream) cannot be upgraded to a WebSocket this way.
Source
Thrown at ext/http/02_websocket.ts:165
// sync with the header-list built above (protocol negotiation, etc.).
let responseHead = "HTTP/1.1 101 Switching Protocols\r\n";
for (let i = 0; i < r.headerList.length; i++) {
const { 0: name, 1: value } = r.headerList[i];
responseHead += `${name}: ${value}\r\n`;
}
responseHead += "\r\n";
if (nodeSocket.destroyed) {
throw new TypeError(
"Socket is already destroyed - cannot upgrade to WebSocket",
);
}
const handle = nodeSocket._handle;
if (!handle) {
throw new TypeError("Socket has no handle - cannot upgrade");
}
if (typeof handle.takeStream !== "function") {
throw new TypeError(
"Socket is not a TCP socket - only TCP connections can be upgraded to WebSocket",
);
}
// Extra bytes that were already buffered (e.g., from the upgrade
// request body that arrived with the headers)
const extraBytes = options.head || new Uint8Array(0);
// Defer setup so the caller can attach event handlers (onopen,
// onmessage, etc.) before events fire.
(async () => {
try {
// Wait for the 101 response to fully flush before taking the
// stream. A fire-and-forget write could leave data in the
// internal_write_queue that would be orphaned once we detach
// the stream from libuv.
await new Promise((resolve, reject) => {
nodeSocket.write(responseHead, (err) => {View on GitHub (pinned to 89f33cbef2)
Solutions
- Serve WebSocket upgrades only on TCP listeners (server.listen({ port }) / listen({ host, port })), not unix sockets/pipes.
- Guard with typeof socket?._handle?.takeStream === 'function' before upgrading.
- For unix-socket IPC, use a different transport (Deno.UnixConn / WebSocket over TCP).
Example fix
// before
const server = createServer();
server.listen("/tmp/app.sock"); // unix socket
server.on("upgrade", (req, socket, head) => {
Deno.upgradeWebSocket(req, { socket, head }); // handle has no takeStream
});
// after
const server = createServer();
server.listen(8080); // TCP
server.on("upgrade", (req, socket, head) => {
if (typeof socket._handle?.takeStream !== "function") return;
const { socket: ws } = Deno.upgradeWebSocket(req, { socket, head });
}); Defensive patterns
Strategy: type-guard
Validate before calling
server.on("upgrade", (req, socket, head) => {
if (typeof (socket as any)?._handle?.takeStream !== "function") return socket.destroy();
Deno.upgradeWebSocket(req, { socket, head });
}); Type guard
function isUpgradableTcpSocket(s: unknown): boolean { const h = (s as { _handle?: { takeStream?: unknown } } | null)?._handle; return typeof h?.takeStream === "function"; } Try / catch
try { Deno.upgradeWebSocket(req, { socket, head }); } catch (e) { if (e instanceof TypeError && e.message.includes("not a TCP socket")) { socket.destroy(); return; } throw e; } Prevention
- Expose WebSocket upgrades only on TCP listeners, not unix sockets/pipes.
- Keep a capability check on takeStream when bridging foreign socket types.
When it happens
Trigger: Calling the upgrade path with a Unix socket or named-pipe connection (server listening on a path); sockets from abstractions whose _handle is a custom object without takeStream; passing a TLS net.Socket whose handle type differs; interop with non-node runtimes that emulate the socket shape only partially.
Common situations: Servers listening on unix domain sockets for sidecar/IPC being reused for WS; test doubles mocking _handle as {}; adapters bridging other socket types into node-compatible shapes; internal-only listeners accidentally exposed to upgrade requests.
Related errors
- Socket is already destroyed - cannot upgrade to WebSocket
- Socket has no handle - cannot upgrade
- Already upgraded
- Unsupported 'alpnProtocols' option provided. 'h2' and 'http/
- Invalid Header: 'upgrade' header must contain 'websocket'
AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16).
Data as JSON: /api/errors/5f32e409841a89b4.
Report an issue: GitHub.