v10.2 Fix layout switching, pane, and high-DPI display issues See what's new

How to Debug WebSocket Connections (Step by Step)

Inspect frames, handshakes, and close codes

To debug WebSocket connections you inspect two things: the HTTP upgrade handshake that opens the channel, and every frame that flows over it afterward. Unlike a request/response pair, a WebSocket is a long-lived, full-duplex connection, so the useful signal is spread across many small messages, control frames, and a final close code. The workflow below covers how the protocol works on the wire, the frame and close-code details that explain real failures, and how to read them in practice.

What to inspect when you debug a WebSocket connection

A complete WebSocket debugging pass looks at four layers, in order:

  1. The handshake - the GET request with Upgrade: websocket and the server's 101 Switching Protocols response. If this fails, no frames are ever exchanged.
  2. Data frames - the Text and Binary messages your application actually sends and receives, plus their direction and timing.
  3. Control frames - Ping/Pong keepalives and the Close frame, which explain idle drops and disconnects.
  4. The close code - the numeric reason the connection ended, which is often the single most useful clue.

How the WebSocket protocol works under the hood

Most connection bugs are easier to read once you know what the bytes mean. WebSocket is defined by RFC 6455, and compression by RFC 7692.

The upgrade handshake

A WebSocket starts life as an ordinary HTTP/1.1 request. The client sends an Upgrade request with a random Sec-WebSocket-Key; the server confirms with 101 Switching Protocols and a matching Sec-WebSocket-Accept. The accept value is not a secret - it is the SHA-1 hash of the client key concatenated with the fixed GUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11, Base64-encoded. A mismatched or missing accept header means a proxy or server mangled the handshake.

GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: permessage-deflate

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

The same handshake also carries the negotiated subprotocol (Sec-WebSocket-Protocol) and extensions (Sec-WebSocket-Extensions). Reading these HTTP headers tells you whether compression was actually agreed, not just requested.

Frame anatomy and opcodes

After the upgrade, data travels in frames rather than messages. Each frame begins with a FIN bit (is this the last fragment?), three reserved bits - RSV1 doubles as the per-message-deflate flag - a 4-bit opcode, a MASK bit, and a payload length encoded in 7, 7+16, or 7+64 bits. The opcode decides how to read the rest:

Opcode Type Purpose
0x0ContinuationNext fragment of a split message
0x1TextUTF-8 text payload
0x2BinaryRaw binary payload
0x8CloseClosing handshake, carries a close code
0x9PingKeepalive probe
0xAPongReply to a ping

Two details trip people up. First, every client-to-server frame is masked: the payload is XOR-ed with a 4-byte key, so a raw capture of an outgoing frame looks like noise until it is unmasked. Server-to-client frames are never masked. Second, a single logical message can be fragmented across one initial frame (FIN=0) and any number of Continuation frames, which is why a payload may appear to arrive in pieces.

Control frames and compression

Ping and Pong frames keep an otherwise idle connection alive; if a load balancer or proxy times out before the next ping, you get an abnormal close. A Close frame carries a 2-byte close code followed by an optional UTF-8 reason string. When permessage-deflate is negotiated, text and binary payloads are DEFLATE-compressed and the RSV1 bit is set; the client_no_context_takeover and server_no_context_takeover parameters control whether the compression window resets between messages. A capture that does not inflate these frames shows compressed bytes instead of your JSON.

WebSocket close codes worth knowing

The close code is defined in RFC 6455 §7.4. These are the ones you will see most while debugging:

Code Name What it usually means
1000Normal ClosureConnection finished cleanly
1001Going AwayServer shutting down or client navigated away
1002Protocol ErrorA frame violated the protocol
1006Abnormal ClosureNo close frame received - network drop, crash, or proxy timeout
1009Message Too BigPayload exceeded the peer's limit
1011Internal ErrorUnhandled exception on the server
1015TLS Handshakewss:// TLS negotiation failed

Codes 1005, 1006, and 1015 are never sent on the wire - the browser or runtime sets them locally when no real close frame arrived. That is why a 1006 never has a reason string: the connection died before a Close frame could be exchanged. To find the cause, you have to look one layer down at the handshake, the last frames before the drop, and the keepalive timing - not at the close event itself.

How to debug WebSocket connections step by step

  1. Confirm the handshake. Verify the request carries Upgrade: websocket and the response is 101 Switching Protocols with a valid Sec-WebSocket-Accept. A 200, 301, or 403 here means the upgrade was blocked or rewritten upstream.
  2. Read the frames in order. Follow sent and received frames on a single timeline. Check opcodes and direction so you can see which side stopped talking and when.
  3. Decode the payload. Inflate permessage-deflate frames and pretty-print JSON so you compare application data, not compressed bytes. Confirm text frames are valid UTF-8.
  4. Check keepalive and close. Look at Ping/Pong spacing for idle drops, and read the Close code and reason to separate a clean shutdown from a network or server failure.

In the browser, Chrome and Firefox DevTools expose this under the Network tab's WS filter, with a Messages view of frames. That works for pages you load yourself. It does not help when the WebSocket lives in a desktop app, a background service, a CLI tool, a mobile emulator, or a server-to-server call - and it cannot show you a connection that opened before DevTools was attached.

Debugging WebSocket frames in HTTP Debugger

HTTP Debugger Pro captures WebSocket traffic system-wide on Windows without a proxy or browser extension, so it sees connections from any process - browsers, desktop apps, services, and CLI tools - including encrypted wss:// and localhost. Because capture is not tied to a browser tab, it also catches connections that were already open before you started looking.

Each WebSocket connection gets its own Messages view: a single timeline of sent and received frames with direction, opcode type, byte size, and a payload preview. You can filter by frame type or direction and search across payloads.

WebSocket Messages view in HTTP Debugger showing sent and received text frames with a JSON payload

The Info tab decodes each frame down to the wire: opcode, FIN/final flag, fragmentation, masking, and the compression state. This is where the permessage-deflate details show up - whether the payload was compressed and successfully decoded - so a frame that looks like garbage in a raw capture reads as plain text here.

Per-frame WebSocket info in HTTP Debugger: opcode, final flag, masked, compressed, and payload decoded fields

For Text frames, the payload is shown as readable text and auto-formatted when it is JSON, with Raw and Hex views alongside. Close frames are parsed into their close code and reason, and Ping/Pong frames appear inline so idle disconnects are easy to trace. WebSocket traffic is read-only here - HTTP Debugger inspects and decodes frames rather than injecting or rewriting them.

Decoded WebSocket text frame shown as formatted JSON in HTTP Debugger

It complements browser tooling: use DevTools for a page you control, and a system-wide HTTP sniffer when the WebSocket lives outside the browser or rides HTTP traffic you cannot attach DevTools to. The same connection's upgrade headers and frames sit next to the rest of your captured session, which makes it a practical HTTP analyzer for real-time protocols.

Common mistakes when debugging WebSockets

  • Treating 1006 as the cause. Code 1006 only says the connection dropped without a close frame. The real reason is in the handshake, the proxy timeout, or the last frames before the drop.
  • Reading compressed frames raw. If permessage-deflate was negotiated, an un-inflated payload looks like binary noise. Decode it before assuming the data is corrupt.
  • Forgetting masking. Outgoing client frames are XOR-masked on the wire, so a raw byte dump of a sent message is not your payload until it is unmasked.
  • Ignoring ping/pong timing. Many "random" disconnects are idle timeouts at a proxy or load balancer between keepalives, not application bugs.
  • Debugging only in the browser. DevTools cannot see WebSockets from desktop apps, services, or connections opened before it attached.

FAQ

How do I see the messages in a WebSocket connection?

Open a frame timeline for the connection - the Messages view in DevTools, or the Messages view in a system-wide capture tool - and read sent and received frames in order, with their opcode, direction, size, and decoded payload.

Why does my WebSocket close with code 1006?

1006 means the connection closed abnormally without a close frame, usually a network drop, a server crash, or a proxy idle timeout. Because it is set locally by the runtime, it never carries a reason - diagnose it from the handshake, keepalive timing, and the frames before the drop.

Can I debug secure wss:// WebSocket traffic?

Yes. After the TLS layer is decrypted - via the browser's TLS key log for packet captures, or a local root certificate in HTTP Debugger - wss:// frames are inspected exactly like plaintext ws:// frames.

Can I debug WebSockets outside the browser?

Browser DevTools only sees WebSockets from pages it is attached to. For desktop apps, background services, CLI tools, or emulators, use a system-wide capture tool such as HTTP Debugger Pro that intercepts traffic from any process on the machine.

Why does my WebSocket payload look like binary garbage?

The connection most likely negotiated permessage-deflate, so frames are DEFLATE-compressed with the RSV1 bit set. Inflate the payload before reading it; a tool that decodes compression shows the original text or JSON instead of compressed bytes.

HTTP Debugger Pro

Windows 11 / 10

A proxy-less HTTP/HTTPS sniffer for Windows that captures traffic system-wide from any process.

Download free 7-day trial

No registration required · Trusted since 2007