nats-io/nats-server · error
${message} (dynamic websocket protocol error, sent to client
Error message
${message} (dynamic websocket protocol error, sent to client as close status 1002) What it means
wsHandleProtocolError sends a WebSocket close frame with status 1002 (protocol error) containing the message to the client, then returns the message as a Go error so the server can close the connection. The message is labeled as a dynamic websocket protocol error surfaced to the client as close status 1002. It indicates the client (or server) violated the WebSocket protocol.
Source
Thrown at server/websocket.go:776
// status in the close message. So using this one instead.
status = wsCloseStatusGoingAway
default:
status = wsCloseStatusInternalSrvError
}
body := wsCreateCloseMessage(status, reason.String())
c.wsEnqueueControlMessageLocked(wsCloseMessage, body)
nbPoolPut(body) // wsEnqueueControlMessageLocked has taken a copy.
}
// Create and then enqueue a close message with a protocol error and the
// given message. This is invoked when parsing websocket frames.
//
// Lock MUST NOT be held on entry.
func (c *client) wsHandleProtocolError(message string) error {
buf := wsCreateCloseMessage(wsCloseStatusProtocolError, message)
c.wsEnqueueControlMessage(wsCloseMessage, buf)
nbPoolPut(buf) // wsEnqueueControlMessage has taken a copy.
return errors.New(message)
}
func wsIsValidCloseStatus(code int) bool {
switch code {
case wsCloseStatusNoStatusReceived, 1004, 1006, wsCloseStatusTLSHandshake:
return false
}
if code < 1000 || code >= 5000 {
return false
}
// 1016-2999 are currently reserved.
if code >= 1016 && code <= 2999 {
return false
}
return true
}
// Create a close message with the given `status` and `body`.View on GitHub (pinned to 3a66a489d2)
Solutions
- Fix the client to send spec-compliant WebSocket frames (correct opcodes, masking, control-frame sizes)
- Inspect the error message text — it is passed through as the close reason and names the exact protocol violation
- Check for intermediaries (proxies, load balancers) that rewrite or buffer websocket frames and bypass them
- Update client and server libraries to compatible versions
Example fix
// before: client sends a text frame where a binary frame is expected ws.send(JSON.stringify(payload)); // after ws.send(new TextEncoder().encode(JSON.stringify(payload))); // binary per NATS websocket protocol
Defensive patterns
Strategy: try-catch
Try / catch
// browser client
ws.onclose = (ev) => {
if (ev.code === 1002) {
console.error('websocket protocol error, server reason:', ev.reason);
// log client frame state and reconnect only after fixing the client frames
}
}; Prevention
- Use a maintained websocket client library instead of hand-rolled framing
- Test clients against the server with wscat or official NATS clients
- Audit proxies/LBs for websocket upgrade support and frame passthrough
- Keep server and client protocol implementations on compatible versions
When it happens
Trigger: Any code path in the server's websocket client handling that detects a protocol violation and calls c.wsHandleProtocolError(msg), e.g. malformed frames, invalid opcodes, bad control frames, or unsupported data during a websocket session.
Common situations: A non-NATS websocket client (browser dev tools, proxies, custom clients) sending malformed frames; intermediaries/proxies mangling websocket traffic; protocol negotiation bugs after server upgrades.
Related errors
- connection rejected since minimum version required is
- MQTT clients over websocket must connect to the Websocket po
- not same origin
- not in the allowed list
- websocket requires TLS configuration
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/bf9725000721bb2c.
Report an issue: GitHub.