Skip to content

admin_startWS can leave two WebSocket endpoints active simultaneously #35483

Description

@BenWhite713

Summary

The documentation states:

Please note, only one WebSocket endpoint is allowed to be active at any time.

This guarantee is enforced only per internal httpServer object. If the existing WebSocket endpoint shares the HTTP server's port, calling admin_startWS with a different port selects the separate n.ws server. The duplicate check does not see the already active endpoint, so both ports remain reachable.

Documentation: https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-admin#admin-startws

Affected revision and environment

  • Runtime reproduction: Geth 1.17.5-unstable, commit 81ab8b594ebe0f672450779d4b308f3f33191828
  • Platform: macOS arm64, Go 1.26.3
  • Initial configuration: HTTP and WebSocket both on port 52387
  • Second endpoint: WebSocket on port 52388
  • The same server-selection and per-server duplicate check remain on master at 7e520c43104fd0447acd7372a63ba1daa5c05929 (checked 2026-08-08).

Steps to reproduce

Start Geth with HTTP and WebSocket sharing one port:

PORT_A=52387

./build/bin/geth \
  --dev \
  --http \
  --http.addr 127.0.0.1 \
  --http.port "$PORT_A" \
  --http.api admin,eth,net,web3 \
  --http.corsdomain '*' \
  --ws \
  --ws.addr 127.0.0.1 \
  --ws.port "$PORT_A" \
  --ws.api admin,eth,net,web3 \
  --ws.origins '*' \
  --ipcdisable

In another shell:

#!/usr/bin/env bash
set -euo pipefail

PORT_A="${PORT_A:-52387}"
PORT_B="${PORT_B:-52388}"
RPC_URL="http://127.0.0.1:${PORT_A}"

echo '=== start a second WebSocket endpoint ==='
curl -sS -H 'Content-Type: application/json' --data "{
  \"jsonrpc\":\"2.0\",
  \"id\":1,
  \"method\":\"admin_startWS\",
  \"params\":[\"127.0.0.1\",${PORT_B},\"*\",\"eth,net,web3,admin\"]
}" "$RPC_URL"
echo

ws_handshake() {
  local port="$1"
  curl --http1.1 -sS -i --max-time 2 \
    -H 'Connection: Upgrade' \
    -H 'Upgrade: websocket' \
    -H 'Sec-WebSocket-Version: 13' \
    -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
    -H 'Origin: http://127.0.0.1' \
    "http://127.0.0.1:${port}/" 2>/dev/null | sed -n '1p' || true
}

echo "=== handshake on original port ${PORT_A} ==="
ws_handshake "$PORT_A"
echo "=== handshake on second port ${PORT_B} ==="
ws_handshake "$PORT_B"

echo '=== same-server duplicate control ==='
curl -sS -H 'Content-Type: application/json' --data "{
  \"jsonrpc\":\"2.0\",
  \"id\":2,
  \"method\":\"admin_startWS\",
  \"params\":[\"127.0.0.1\",${PORT_A},\"*\",\"eth,net,web3,admin\"]
}" "$RPC_URL"
echo

Observed behavior

Starting the second endpoint returned success:

{"jsonrpc":"2.0","id":1,"result":true}

Both ports completed the WebSocket upgrade at the same time:

HTTP/1.1 101 Switching Protocols
HTTP/1.1 101 Switching Protocols

Repeating admin_startWS on the original port correctly exercised the local duplicate guard:

{
  "error": {
    "code": -32000,
    "message": "JSON-RPC over WebSocket is already enabled"
  }
}

Expected behavior

If only one WebSocket endpoint may be active, the second admin_startWS call should fail or replace the existing endpoint. It should not leave both listener addresses reachable.

Relevant code

Impact

Operators or automation relying on the single-endpoint guarantee can unintentionally leave an older WebSocket RPC endpoint reachable while opening another one. This can increase the exposed RPC surface and cause configuration or shutdown logic to reason about the wrong listener.

Suggested direction

Enforce WebSocket endpoint uniqueness at the Node level before selecting an internal server object, or revise the API contract if multiple endpoints are intentionally supported.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions