nodejs/node · error · SocketError

UND_ERR_SOCKET

UND_ERR_SOCKET

Error message

bad upgrade

What it means

Thrown as a SocketError (code UND_ERR_SOCKET) in two upgrade()-related cases: (1) onResponseStart fires, meaning the server returned a normal HTTP response instead of the expected protocol switch — a hard 'bad upgrade'; (2) onRequestUpgrade receives a status code other than 101 (HTTP/1.1) or 200 (HTTP/2), aborting the controller with this error. It indicates the server did not honor the Upgrade/CONNECT request.

Source

Thrown at deps/undici/src/lib/api/api-upgrade.js:50

    this.context = null

    addSignal(this, signal)
  }

  onRequestStart (controller, context) {
    if (this.reason) {
      controller.abort(this.reason)
      return
    }

    assert(this.callback)

    this.abort = (reason) => controller.abort(reason)
    this.context = context
  }

  onResponseStart () {
    throw new SocketError('bad upgrade', null)
  }

  onRequestUpgrade (controller, statusCode, headers, socket) {
    const expectedStatusCode = socket[kHTTP2Stream] === true ? 200 : 101

    if (statusCode !== expectedStatusCode) {
      const socketInfo = socket[kHTTP2Stream] === true ? null : util.getSocketInfo(socket)
      controller.abort(new SocketError('bad upgrade', socketInfo))
      return
    }

    const { callback, opaque, context } = this

    removeSignal(this)

    this.callback = null

    const rawHeaders = controller?.rawHeaders

View on GitHub (pinned to 1b2de5e052)

Solutions

  1. Verify the target server actually supports the protocol you are upgrading to and the URL/path is correct.
  2. Send all required upgrade headers (e.g. Connection: Upgrade, Upgrade: websocket, Sec-WebSocket-Key, Sec-WebSocket-Version: 13).
  3. Check that no proxy/load balancer in the path strips or rewrites the Upgrade/Connection headers.
  4. Wrap the upgrade call in try/catch and fall back to a non-upgraded transport or retry against a known-good endpoint.

Example fix

// before
client.upgrade({ path: '/api/data', headers: { Upgrade: 'websocket' } }, cb)

// after
client.upgrade({
  path: '/ws',
  headers: {
    Connection: 'Upgrade',
    Upgrade: 'websocket',
    'Sec-WebSocket-Key': key,
    'Sec-WebSocket-Version': '13'
  }
}, cb)
Defensive patterns

Strategy: try-catch

Validate before calling

function buildUpgradeHeaders(protocol) {
  if (protocol === 'websocket') {
    return {
      Connection: 'Upgrade',
      Upgrade: 'websocket',
      'Sec-WebSocket-Key': crypto.randomBytes(16).toString('base64'),
      'Sec-WebSocket-Version': '13'
    }
  }
  return { Connection: 'Upgrade', Upgrade: protocol }
}
// client.upgrade({ path: '/ws', headers: buildUpgradeHeaders('websocket') }, cb)

Try / catch

try {
  client.upgrade(opts, (err, data) => { if (err) handleBadUpgrade(err); else use(data.socket) })
} catch (e) {
  if (e.code === 'UND_ERR_SOCKET' && /bad upgrade/.test(e.message)) {
    // server did not switch protocols; fall back or surface a clear error
  } else throw e
}

Prevention

When it happens

Trigger: Requesting an upgrade to a server/endpoint that does not support the requested protocol (e.g. WebSocket upgrade against a plain JSON endpoint); missing or wrong Upgrade/Connection headers; server returns 200 with a body instead of 101; HTTP/2 upgrade returning non-200.

Common situations: Pointing a WebSocket client at the wrong URL scheme/path; server requires a specific Sec-WebSocket-Key/Subprotocol header that was omitted; reverse proxy/load balancer stripping Upgrade headers; HTTP/2 back-end returning 404/500 during connect.

Related errors


AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13). Data as JSON: /api/errors/072ac3b84918fdc1. Report an issue: GitHub.