websockets/ws · critical · Error

server.handleUpgrade() was called more than once with the sa

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() (lib/websocket-server.js:378-383) when the same network socket has already been upgraded and tagged via socket[kWebSocket]. This happens when handleUpgrade() is invoked more than once for a single TCP socket, which the library treats as a misconfiguration because a socket can only back one WebSocket connection. It is a synchronous throw from within handleUpgrade() and will crash the process if uncaught.

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 ae1de54330)

Solutions

  1. Ensure each 'upgrade' event is handled by exactly one WebSocketServer: use the path option or a custom shouldHandle/verifyClient to route.
  2. If you need two servers on one HTTP server, branch in your single 'upgrade' listener and call handleUpgrade on only one of them.
  3. Avoid registering multiple 'upgrade' listeners that all call handleUpgrade unconditionally.

Example fix

// before
httpServer.on('upgrade', (req, socket, head) => {
  wss1.handleUpgrade(req, socket, head, () => {});
  wss2.handleUpgrade(req, socket, head, () => {}); // double upgrade!
});

// after
httpServer.on('upgrade', (req, socket, head) => {
  const { pathname } = new URL(req.url, 'http://x');
  if (pathname === '/a') wss1.handleUpgrade(req, socket, head, () => {});
  else wss2.handleUpgrade(req, socket, head, () => {});
});
Defensive patterns

Strategy: validation

Validate before calling

httpServer.on('upgrade', (req, socket, head) => {
  if (socket[kWebSocket]) return; // already upgraded, ignore
  const { pathname } = new URL(req.url, 'http://x');
  const target = pathname === '/a' ? wss1 : wss2;
  target.handleUpgrade(req, socket, head, (ws) => {/* ... */});
});

Type guard

const { kWebSocket } = require('ws/lib/constants');
function isSocketUpgradeable(socket) {
  return !socket[kWebSocket];
}

Try / catch

try {
  wss.handleUpgrade(req, socket, head, cb);
} catch (err) {
  if (/more than once with the same socket/.test(err.message)) {
    // double upgrade: ensure only one WSS handles this socket
    socket.destroy();
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Attaching two or more WebSocketServer instances to the same HTTP server's 'upgrade' event and both calling handleUpgrade on the same req/socket; calling wss.handleUpgrade() manually in a loop or in overlapping branches of an if/else; a single server with both { server } mode and a manual 'upgrade' handler that both forward to handleUpgrade.

Common situations: Running multiple WebSocket endpoints on one HTTP server without using the path option or shouldHandle to discriminate; copy-pasting the noServer example twice; middleware that re-emits 'upgrade' events; upgrading from one WSS to two without splitting upgrade handling.

Related errors


AI-assisted analysis of websockets/ws@ae1de54330 (2026-08-03). Data as JSON: /data/errors/7a13157ac0069a3d.json. Report an issue: GitHub.