WebSockets, the Handshake, and How to Test Them

Most of the web is request-response: the browser asks, the server answers, the connection closes. WebSockets break that pattern. They hold one TCP connection open and let both sides send messages at any time — the foundation of chat apps, live sports scores, collaborative documents, trading dashboards, and multiplayer games. If you build or maintain anything real-time, you will eventually stare at a dead socket wondering what happened. This guide explains how WebSockets work, what the handshake and close codes mean, and how to test a server step by step with ToolsOfWeb's WebSocket Tester.

How a WebSocket connection starts

A WebSocket begins life as an ordinary HTTP request with an Upgrade header asking to switch protocols. The server answers 101 Switching Protocols, and from that moment the HTTP semantics are gone: the same TCP connection now carries WebSocket frames in both directions until somebody closes it. That upgrade dance is why a WebSocket URL looks like ws:// or wss:// instead of http — same idea, different scheme.

The wss:// variant wraps everything in TLS, exactly like https does for http. Two rules follow from this: browsers refuse ws:// connections from https:// pages (mixed content), and anything carrying credentials in production should be wss:// — a plain-text socket leaks every message to anyone watching the network. Localhost is the one exception browsers allow, which is why ws://localhost:3000 works during development.

During the handshake the client can also propose subprotocols (like graphql-ws) and extensions (like permessage-deflate compression). The server picks at most one subprotocol and confirms the extensions it supports. If your client asked for a subprotocol and the server silently ignored it, that mismatch is a classic source of “connected but nothing works” bugs — always check what was actually negotiated.

Close codes: the socket’s last words

Every WebSocket close carries a numeric code, and learning the handful that matter will shortcut most debugging sessions:

  • 1000 — normal closure. A polite hang-up; nothing is wrong.
  • 1001 — going away. The server is restarting or the page navigated away.
  • 1006 — abnormal closure. No close frame ever arrived: the network died, the process crashed, or a middlebox killed the connection. When users report “it just disconnected,” this is usually the code.
  • 1008 — policy violation. Frequently an auth rejection wearing a mask: expired tokens and missing credentials land here.
  • 1009 — message too big. The payload crossed the server’s frame limit; chunk the data or raise the limit.
  • 1011 — internal error. The server’s message handler threw an exception.
  • 1015 — TLS handshake failure. The secure upgrade never completed; check certificates and cipher support.

The discipline is simple: read the code before the logs. A 1008 sends you to authentication, a 1009 to message size, a 1006 to the network — three different investigations, chosen in seconds instead of hours.

Testing a WebSocket server, step by step

  1. Sanity-check your client with an echo server. Open the WebSocket Tester, click the Postman Echo preset, and connect. Send {"hello":"websocket"} — the server echoes it back. If this works, your browser, network, and the client are all fine.
  2. Connect to your own server. Paste your ws:// or wss:// URL and connect. Note the status indicator and the handshake panel: confirm the subprotocol and extensions match what your server should negotiate.
  3. Send your real messages. Paste an actual client message — a subscribe frame, an auth token payload, a chat message — and watch the response. The log timestamps every entry and shows byte sizes, so oversized payloads stand out immediately.
  4. Measure latency. Click “Measure latency” to send a timestamped probe and time the echo. Browsers don’t expose raw ping frames, so this echo-based round trip is the honest number.
  5. Break it on purpose. Send an oversized message, kill the server mid-session, let the token expire. Watch which close code each failure produces — you’re building a mental map of your server’s failure modes.
  6. Export the session. Download the log as JSON and attach it to the bug report. “Here is the exact message sequence and the 1008 that followed” beats “it disconnected sometimes” every time.

Debugging tips worth internalizing

  • Instant 1006 on connect? Check the scheme first (ws vs wss mismatch), then CORS-adjacent proxy rules, then whether the server process is actually listening.
  • Connected but silent? Verify the subprotocol negotiation and that your message format matches what the server parses — a JSON server receiving plain text often just drops it.
  • Disconnects after ~60 seconds idle? A proxy or load balancer is timing out idle connections. Either send periodic application-level heartbeats or raise the idle timeout.
  • Works locally, fails in production? Almost always TLS (wss) or a reverse proxy that doesn’t forward Upgrade headers. Nginx needs explicit proxy_set_header Upgrade and Connection directives.
  • Validate your payloads separately. Paste the JSON you’re about to send into the JSON Formatter first — half of all “server ignores my message” bugs are malformed JSON the sender never noticed.

A note on privacy

The tester is a thin interface over your browser’s native WebSocket API: the socket opens directly between your device and the URL you enter. No proxy, no message logging, nothing stored. That makes it safe for staging environments and internal endpoints — the traffic never takes a detour through a third party.

Ready to poke at a socket? Open the WebSocket Tester — free, no account, and the echo preset is one click away.

FAQs

What is a WebSocket?+

A protocol that keeps a single TCP connection open between browser and server so both sides can send messages at any time. It starts as an HTTP request that “upgrades” to a persistent socket, and it underpins chat apps, live dashboards, and real-time APIs.

What is the difference between ws and wss?+

ws:// is plain WebSocket; wss:// is WebSocket over TLS — the same relationship as http vs https. Browsers block ws:// from https:// pages (mixed content), so production servers should always use wss://.

What does close code 1006 mean?+

Abnormal closure: the connection died without a close frame. The network failed, the server crashed, or a firewall killed the socket. It is the most common “why did it disconnect?” answer and the first thing to rule out.

How do I test a WebSocket server without writing code?+

Use an online WebSocket tester: paste your ws/wss URL, connect, and send messages from the composer. Public echo servers let you verify the client works before you blame your server.

Why do browsers not support WebSocket ping frames?+

The browser WebSocket API deliberately hides ping/pong — the browser itself answers pings automatically. To measure latency from JavaScript, send a timestamped message and time the echo instead.