websockets/ws · critical · Error
server.handleUpgrade() was called more than once with the…
Error message
server.handleUpgrade() was called more than once with the same socket, possibly due to a misconfiguration
What it means
Thrown by WebSocketServer.completeUpgrade() at websocket-server.js:378-383 when the same network socket already has a WebSocket associated with it (socket[kWebSocket] is set). This symbol is assigned during setSocket() at websocket.js:233, so seeing it means completeUpgrade already ran for this socket. The library treats a second upgrade on one socket as a configuration error, not a normal event.
Solutions
- Ensure only ONE WebSocketServer (or one manual handleUpgrade call) processes each upgrade request. Use options.path to route by URL, or use noServer mode with explicit routing in your own 'upgrade' handler.
- If you need multiple WS endpoints on one HTTP server, use noServer: true and dispatch based on req.url in a single 'upgrade' listener: if (req.url === '/a') wssA.handleUpgrade(...); else wssB.handleUpgrade(...).
- Remove any duplicate 'upgrade' listener you added manually if the server already auto-attaches one (server/noServer modes do this in the constructor at websocket-server.js:125-131).
Example fix
// before — two servers on one HTTP server both grab every upgrade
const wssA = new WebSocketServer({ server: httpServer, path: '/a' });
const wssB = new WebSocketServer({ server: httpServer, path: '/b' });
// (path helps, but safest is noServer routing)
// after — single noServer instance with manual routing
const wssA = new WebSocketServer({ noServer: true });
const wssB = new WebSocketServer({ noServer: true });
httpServer.on('upgrade', (req, socket, head) => {
const { pathname } = new URL(req.url, 'http://x');
if (pathname === '/a') wssA.handleUpgrade(req, socket, head, cb);
else if (pathname === '/b') wssB.handleUpgrade(req, socket, head, cb);
else socket.destroy();
}); Defensive patterns
Strategy: validation
Validate before calling
// Use noServer mode with explicit routing to avoid double-dispatch
function attachWebSocketServers(httpServer, servers) {
httpServer.on('upgrade', (req, socket, head) => {
const { pathname } = new URL(req.url, 'http://placeholder');
const wss = servers.find(s => s.options.path === pathname);
if (wss) {
wss.handleUpgrade(req, socket, head, (ws) => { /* ... */ });
} else {
socket.destroy();
}
});
} Type guard
function isSocketAlreadyUpgraded(socket) {
return socket[kWebSocket] !== undefined;
} Try / catch
try {
wss.handleUpgrade(req, socket, head, cb);
} catch (err) {
if (/more than once with the same socket/.test(err.message)) {
// a prior handler already upgraded this socket; just destroy to be safe
socket.destroy();
} else throw err;
} Prevention
- Use noServer mode and a single 'upgrade' listener with explicit URL-based routing when you need multiple endpoints on one HTTP server.
- Never attach two WebSocketServer instances to the same httpServer without disjoint path filters.
- Don't call handleUpgrade manually if the server already auto-attaches its own upgrade listener (port/server modes).
When it happens
Trigger: Two WebSocketServer instances both attach their 'upgrade' listener to the same httpServer and both call handleUpgrade for the same request; a user manually calls wss.handleUpgrade() in their own 'upgrade' listener while the server also has its auto-attached listener; an 'upgrade' event fires twice on the same socket due to a proxy or framework quirk.
Common situations: Mounting multiple WebSocketServer instances on one HTTP server without a path filter (options.path) or without noServer routing; a framework (e.g. Express/Next/Fastify adapter) that adds its own upgrade handler that double-dispatches; accidentally calling handleUpgrade inside both verifyClient's callback and the default path.
Related errors
- One and only one of the "port", "server", or "noServer"…
- The server is operating in "noServer" mode
- Invalid URL
- Unsupported protocol version
AI-assisted analysis of websockets/ws@c791e707ea (2026-08-06).
Data as JSON: /api/errors/7a13157ac0069a3d.
Report an issue: GitHub.
Appendix: source
Thrown at lib/websocket-server.js:379
*
* @param {Object} extensions The accepted extensions
* @param {String} key The value of the `Sec-WebSocket-Key` header
* @param {Set} protocols The subprotocols
* @param {http.IncomingMessage} req The request object
* @param {Duplex} socket The network socket between the server and client
* @param {Buffer} head The first packet of the upgraded stream
* @param {Function} cb Callback
* @throws {Error} If called more than once with the same socket
* @private
*/
completeUpgrade(extensions, key, protocols, req, socket, head, cb) {
//
// Destroy the socket if the client has already sent a FIN packet.
//
if (!socket.readable || !socket.writable) return socket.destroy();
if (socket[kWebSocket]) {
throw new Error(
'server.handleUpgrade() was called more than once with the same ' +
'socket, possibly due to a misconfiguration'
);
}
if (this._state > RUNNING) return abortHandshake(socket, 503);
const digest = createHash('sha1')
.update(key + GUID)
.digest('base64');
const headers = [
'HTTP/1.1 101 Switching Protocols',
'Upgrade: websocket',
'Connection: Upgrade',
`Sec-WebSocket-Accept: ${digest}`
];
View on GitHub (pinned to c791e707ea)