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?.rawHeadersView on GitHub (pinned to 1b2de5e052)
Solutions
- Verify the target server actually supports the protocol you are upgrading to and the URL/path is correct.
- Send all required upgrade headers (e.g. Connection: Upgrade, Upgrade: websocket, Sec-WebSocket-Key, Sec-WebSocket-Version: 13).
- Check that no proxy/load balancer in the path strips or rewrites the Upgrade/Connection headers.
- 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
- Confirm the server endpoint supports the protocol before attempting an upgrade.
- Send the full required header set (Connection, Upgrade, protocol-specific headers).
- Ensure no proxy/load balancer strips Upgrade/Connection headers.
- Wrap upgrade() in try/catch and handle the SocketError explicitly.
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.