Skip to content

Websockets

WebSockets provide persistent connections for real-time blockchain updates.


FeatureHTTPWebSocket
ConnectionRequest-ResponsePersistent
LatencyHigherLower
Real-timePolling neededPush-based
Use caseQueriesLive data

const ws = new WebSocket('ws://localhost:8546');
ws.onopen = () => {
ws.send(JSON.stringify({
jsonrpc: '2.0',
method: 'eth_subscribe',
params: ['newHeads'],
id: 1
}));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('New block:', data.params.result);
};
ws.send(JSON.stringify({
jsonrpc: '2.0',
method: 'eth_subscribe',
params: ['newPendingTransactions'],
id: 2
}));

Terminal window
geth \
--ws \
--ws.addr 0.0.0.0 \
--ws.port 8546 \
--ws.api eth,net,web3 \
--ws.origins "*"
# ⚠️ geth has no --ws.ratelimit flag — per-client WS limits are set at the
# reverse proxy (Nginx/HAProxy), see Chapter 22.

WebSockets are not a blockchain concept — they are a standard full-duplex protocol (RFC 6455). Nodes use them as a transport for the same JSON-RPC methods as HTTP.

┌─────────────────────────────────────────────────────────────────┐
│ WEBSOCKET LIFECYCLE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ CLIENT SERVER (geth WS) │
│ │ │ │
│ │ HTTP Upgrade Request │ │
│ ├──────────────────────────▶│ 101 Switching Protocols │
│ │◀──────────────────────────┤ │
│ │ ★ CONNECTION OPEN │ │
│ │ │ │
│ │ JSON-RPC: eth_subscribe │ │
│ │ {"params":["newHeads"]} │ │
│ ├──────────────────────────▶│ │
│ │ │ subscription id: "0x1" │
│ │◀──────────────────────────┤ │
│ │ │ │
│ │ push: new block header │ │
│ │◀──────────────────────────┤ │
│ │ push: new block header │ │
│ │◀──────────────────────────┤ │
│ │ │ │
│ │ JSON-RPC: eth_unsubscribe│ │
│ ├──────────────────────────▶│ │
│ │◀──────────────────────────┤ true │
│ │ CLOSE frame │ │
│ ├──────────────────────────▶│ │
│ │ │ │
└─────────────────────────────────────────────────────────────────┘

Key mechanics every operator should know:

  • Upgrade only: a WS session starts as a plain HTTP request with an Upgrade: websocket header; the server responds 101.
  • Frames: messages travel in frames (text/binary). JSON-RPC over WS is text frames; binary frames keep room for compression (Eth/65-era wire optimizations).
  • Heartbeats (ping/pong): proxies, LBs and clients send ping frames; the peer must answer pong or the connection is dropped. Long-idle subscriptions can be killed by firewalls/LBs that don’t see traffic — many apps send a ping/alive signal.
  • Ordering: WS is ordered — you will not see a reordering of events within one connection, which is why WS is preferred for pendingTransactions/reorg-sensitive apps over polling.

21.5 Subscription Types (Ethereum eth_subscribe)

Section titled “21.5 Subscription Types (Ethereum eth_subscribe)”
SubscriptionparamsPayloadUse case
New block headers["newHeads"]{parentHash, number, ...}Block watchdogs, indexers
Pending transactions["newPendingTransactions"]tx hashMempool monitoring, front-running detection
Logs["logs", {address, topics}]matching event logsDapp event streams, indexers
Syncing["syncing"]{syncing, status}Detect sync state changes live
// Subscribe to logs for a specific contract
ws.send(JSON.stringify({
jsonrpc: '2.0',
method: 'eth_subscribe',
params: ['logs', { address: '0xContractAddress', topics: [] }],
id: 3
}));

--ws.origins whitelists browser origins. Use exact origins, not "*", in production:

Terminal window
--ws.origins "https://app.example.com,https://admin.example.com"

Load Balancers Must Keep Connections Alive

Section titled “Load Balancers Must Keep Connections Alive”

WS is a long-lived connection — an LB that round-robins by HTTP request will break it. Use sticky sessions / IP hash / least-connections for the WS backend, and set the idle timeout higher than your longest expected silent subscription (or rely on ping/pong to keep the path alive).

# nginx — WS sticky by IP hash, generous timeout
upstream geth_ws {
ip_hash;
server 10.0.0.1:8546;
server 10.0.0.2:8546;
}
server {
listen 8546;
location / {
proxy_pass http://geth_ws;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
}
}

Terminate TLS at the proxy (chapter 34) and keep WS behind it: wss:// in production, never plain ws:// over the public internet — subscriptions leak tx hashes and contract events.

Connections drop; clients must reconnect and re-send eth_subscribe (subscriptions are not persisted server-side). Expect at-least-once semantics: on reconnect your app will see a gap, so re-fetch missed blocks after resubscribing.


“WebSockets give push-based, ordered, low-latency updates, but they cost server resources per connection and are stateful — LB must be sticky, clients must handle ping/pong, reconnect and resubscribe, and production traffic goes over wss://. I treat subscription churn (connect/disconnect rate) as a monitoring signal for misbehaving clients.”


  • WebSockets enable real-time updates
  • Subscribe to new blocks, pending transactions
  • Lower latency than HTTP polling

Last Updated: 2026-10-07