Repository navigation
Dev server crashes with unhandled ECONNRESET when a WebSocket upgrade request lacks the vite-hmr/vite-ping subprotocol #23727
Description
Describe the bug
A WebSocket upgrade request to the Vite dev server without the vite-hmr (or vite-ping) subprotocol leaves the underlying socket without an error listener. When the client then resets the connection, the socket emits ECONNRESET with nothing listening, and the entire dev server process exits with an unhandled 'error' event.
Any stray WebSocket client — a port scanner, a misconfigured tool, or a first attempt at connecting to the HMR socket without knowing the subprotocol requirement — kills the dev server.
Root cause (analysis by @shinshin86)
This was first reported at shinshin86/mesh-avatar-studio#22, where @shinshin86 narrowed it down:
- Vite's dev server only handles WebSocket upgrade requests whose
Sec-WebSocket-Protocolisvite-hmrorvite-ping. Any other upgrade request is left untouched, and no error listener is attached to its socket. - When the client resets the connection, the socket emits
ECONNRESETwith nothing listening, and Node.js exits. - They confirmed the crash with a bare Vite 7.3.6 project (only an
index.html) on macOS / Node.js 22, and confirmed the same handling is still present in the latest Vite (8.3.4).
I independently reproduced it on Windows 11 / Node.js 24.15.0 (7.3.6) and Linux / Node.js 22.23.2 (bare 7.3.6 project, nothing else installed) — the crash is identical on all three platforms.
Reproduction
npm init -y && npm install vite@7.3.6
echo '<!doctype html><title>bare</title>' > index.html
npx vite --port 5199 --strictPortThen, from another process, send a WebSocket upgrade without the subprotocol header and reset the connection:
import socket, struct, time
s = socket.create_connection(("127.0.0.1", 5199))
s.sendall(b"GET / HTTP/1.1\r\nHost: 127.0.0.1:5199\r\nUpgrade: websocket\r\n"
b"Connection: Upgrade\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
b"Sec-WebSocket-Version: 13\r\n\r\n") # no Sec-WebSocket-Protocol
time.sleep(1.0)
s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack("ii", 1, 0))
s.close() # forces RSTA plain JS client also triggers it on Windows/macOS (process exit with a handshake-less socket sends RST there):
new WebSocket("ws://127.0.0.1:5199/"); // no subprotocol; kill the process after a few secondsControl: connecting with the vite-hmr subprotocol works fine — the socket is owned by Vite's HMR layer and closes cleanly.
Logs
node:events:497
throw er; // Unhandled 'error' event
^
Error: read ECONNRESET
at TCP.onStreamRead (node:internal/stream_base_commons:216:20)
Emitted 'error' event on Socket instance at:
at emitErrorNT (node:internal/streams/destroy:170:8)
at emitErrorCloseNT (node:internal/streams/destroy:129:3)
at process.processTicksAndRejections (node:internal/process/task_queues:89:21) {
errno: -104,
code: 'ECONNRESET',
syscall: 'read'
}Expected behavior
The dev server should reject or ignore WebSocket upgrade requests that don't match its subprotocols — e.g. attach an error listener to unhandled upgrade sockets (or destroy them immediately) — instead of crashing the whole process.
System Info
vite: 7.3.6 (also present in 8.3.4 per @shinshin86's check)
Node.js: v24.15.0 (Windows 11), v22 (macOS), v22.23.2 (Linux)Validations
- Follow the Code of Conduct
- Read the Contributing Guidelines.
- Read the docs.
- Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- Make sure this is a Vite issue and not a framework-specific issue. Reproduced with a bare Vite project (only
index.html). - Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.
- The provided reproduction is a minimal reproducible example of the bug.
Activity