What a WebSocket is
A WebSocket is a long-lived connection over which a browser and a server can each send messages whenever they like. It starts as an ordinary HTTP request asking to upgrade. Once the server agrees with status 101 Switching Protocols, the same TCP connection carries WebSocket messages instead of HTTP.
In plain HTTP, the server can only answer requests. With a WebSocket, the server can push a chat message or a price change the moment it happens. Each message costs a few bytes of framing instead of a full set of HTTP headers.
WebSocket URLs use ws:// or wss://. Use wss:// in production. It runs over TLS on port 443 like HTTPS, which protects the messages and avoids proxies that mishandle unencrypted WebSocket traffic. The protocol is defined in RFC 6455.
The upgrade handshake
The client sends a GET request with Upgrade: websocket and Connection: Upgrade headers, a random Sec-WebSocket-Key and Sec-WebSocket-Version: 13. Browsers also send an Origin header naming the origin of the page that opened the socket.
The server answers 101 and includes Sec-WebSocket-Accept, a hash of the client's key combined with a fixed string from the RFC. That value only shows the server understood the WebSocket request; it does not authenticate anyone.
The exchange below is adapted from the example in RFC 6455. Over HTTP/2 and HTTP/3, WebSockets can open with an extended CONNECT request instead of Upgrade (RFC 8441 and RFC 9220).
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: https://example.com
Sec-WebSocket-Version: 13
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Messages, frames and closing the connection
After the handshake, either side can send at any time. Messages are text (UTF-8) or binary and travel in frames with a header of 2 to 14 bytes. Frames from the client are masked with a random key, which stops crafted traffic from confusing caching proxies along the way.
Control frames manage the connection itself. Ping and pong frames check that the other side is still there. A close frame carries a status code, such as 1000 for a normal closure or 1001 when a page is going away.
The protocol leaves acknowledgements, request and response matching, reconnection and message history to the application or a library. Plan for disconnects from the start: reconnect with a backoff delay and fetch whatever was missed while offline.
const socket = new WebSocket('wss://example.com/chat');
socket.addEventListener('open', () => {
socket.send(JSON.stringify({ type: 'join', room: 'general' }));
});
socket.addEventListener('message', (event) => {
console.log('received', event.data);
});
socket.addEventListener('close', (event) => {
console.log('closed with code', event.code);
});When to use WebSockets, and when something simpler fits
WebSockets fit when both sides send often and delay matters, as in chat, multiplayer games and collaborative editing with live cursors. Live dashboards fit too when the client also changes what it subscribes to. Many features only need updates in one direction, and simpler options handle those with less machinery.
- Server-Sent Events (SSE) stream text events from server to client over a normal HTTP response, read in the browser with EventSource. The browser reconnects automatically and can resume from the last event ID, which suits notifications, activity feeds, progress updates and streamed AI responses.
- Over HTTP/1.1, browsers allow only about six connections per domain, which limits SSE across many open tabs. HTTP/2 avoids the problem by carrying many streams on one connection.
- Polling, requesting updates on a timer, is the simplest option when updates are infrequent and a few seconds of delay is acceptable. Long polling holds each request open until there is news, which cuts the delay.
- Webhooks cover the server-to-server case: the sending service makes an HTTP request to your endpoint when an event happens.
Proxies, timeouts and scaling
Every proxy and load balancer in the path has to support the upgrade, and some need configuration. Nginx, for example, only passes WebSockets when told to forward the Upgrade and Connection headers over HTTP/1.1.
Idle connections get closed. Proxies, load balancers and NAT routers drop connections that carry no traffic for a set period, which is 60 seconds by default in nginx. Send heartbeat messages more often than the shortest timeout in the path.
Open connections are server state. A deploy or restart drops them all, and clients reconnecting at the same moment can overload the new servers, so add a random delay to reconnects. Sending a message to users connected to different servers needs a shared channel, such as a message broker.
Authenticating WebSocket connections
The browser WebSocket API cannot set custom headers such as Authorization. Apps either rely on the session cookie, which the browser sends with the handshake, or pass a token in the URL or the first message. Tokens in URLs can end up in server and proxy logs, so make them short-lived and single-use.
Cookies bring a risk called cross-site WebSocket hijacking. The same-origin policy and CORS do not block WebSocket connections, so a page on another site can open one with your users' cookies. The server has to check the Origin header against an allowlist during the handshake.
Authorization checked at the handshake can go stale on a connection that lasts hours. Close or recheck connections when sessions expire or permissions change, and validate every incoming message as untrusted input.