Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
A Socket.IO connection error can mean the server is unreachable, the browser is blocked by CORS, the client and server use incompatible protocols, or a proxy is preventing the WebSocket upgrade. Start by capturing the full connect_error and testing the Engine.IO endpoint; then fix the failure at the stage where it occurs.
Start with a known-good connection
Socket.IO must be attached to the same HTTP server that listens for connections. With Express, create the HTTP server explicitly and call listen() on that server—not on a separate server created by app.listen().
const http = require("node:http");
const express = require("express");
const { Server } = require("socket.io");
const app = express();
const httpServer = http.createServer(app);
const io = new Server(httpServer, {
cors: { origin: "http://localhost:5173" },
});
io.on("connection", (socket) => {
console.log("client connected:", socket.id);
socket.on("disconnect", (reason) => {
console.log("client disconnected:", reason);
});
});
httpServer.listen(3000, "0.0.0.0", () => {
console.log("Socket.IO server listening on port 3000");
});
In the browser, use the Socket.IO client package:
import { io } from "socket.io-client";
const socket = io("http://localhost:3000");
socket.on("connect", () => {
console.log("connected:", socket.id);
});
socket.on("connect_error", (err) => {
console.error("Socket.IO connection failed:", {
message: err.message,
description: err.description,
context: err.context,
type: err.type,
});
});
Socket.IO’s server initialization attaches to a Node HTTP server, which handles the underlying HTTP requests and upgrade events. See the Socket.IO server initialization guide and Node.js HTTP documentation.
Read the error as a clue
| Symptom | Likely area to investigate |
|---|---|
ERR_CONNECTION_REFUSED |
No reachable listener at the requested host and port; check the process, URL, network, and container port. |
xhr poll error |
The initial HTTP polling request failed. Inspect its URL, status, response, CORS headers, and proxy routing. |
| Browser reports a CORS error | The Socket.IO response does not allow the page’s exact origin, or the request is reaching a different server. |
| Polling works, but WebSocket fails | Look at proxy upgrade headers, TLS, firewall policy, and whether the deployment supports WebSocket. |
400 Bad Request |
Possible causes include a wrong path, unsupported protocol, unknown session, malformed request, or multi-instance routing. |
connect_error: Unauthorized or another application message |
Check Socket.IO middleware, namespace middleware, and the client’s authentication payload. |
Unsupported protocol version |
The client and server do not support compatible Socket.IO/Engine.IO protocol versions. |
| Repeated reconnection attempts | The connection is still failing. Find the first failed request and its connect_error; retries are not the underlying fix. |
connect_error can report a low-level connection problem or a rejection by server-side middleware. It is not, by itself, proof of a CORS failure. The client socket event reference and middleware guide describe these cases.
#1 Best Overall
Test the server before changing client options
Socket.IO normally starts with Engine.IO HTTP long-polling and may upgrade the session to WebSocket. The default HTTP endpoint is /socket.io/. Test that endpoint directly:
curl -i "http://localhost:3000/socket.io/?EIO=4&transport=polling"
A reachable, correctly configured Socket.IO server should return an Engine.IO handshake payload, including a session ID and transport details. The exact payload varies. If the command is refused or times out, investigate the listener and network first. A 404 or ordinary application HTML response suggests the wrong path, proxy route, or target server. A 400 needs further inspection: it can reflect protocol incompatibility, a bad request, an unknown session, or load-balancer routing—not just a version mismatch. The Engine.IO protocol documents the handshake and session behavior.
Useful local checks include:
# Check whether a process is listening on port 3000
lsof -nP -iTCP:3000 -sTCP:LISTEN
# Linux alternative
ss -ltnp | grep 3000
# Check the application and Socket.IO endpoint
curl -i http://127.0.0.1:3000/
curl -i "http://127.0.0.1:3000/socket.io/?EIO=4&transport=polling"
For startup visibility, log the actual listener address and failures:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
httpServer.on("listening", () => {
console.log("Listening at:", httpServer.address());
});
httpServer.on("error", (err) => {
console.error("HTTP server error:", err);
});
Fix host, port, and server initialization problems
If the handshake test returns connection refused, check that Node started successfully, is listening on the expected port, and has not crashed before calling listen(). Confirm you are running the intended entry file and that a process manager is not repeatedly restarting a failing process.
A common Express mistake is attaching Socket.IO to one HTTP server and then starting another:
const app = express();
const server = http.createServer(app);
const io = new Server(server);
// Incorrect for this setup: starts a different server
app.listen(3000);
Use server.listen(3000) instead. Calling app.listen() is not inherently wrong for an Express-only application; it is wrong here because Socket.IO is attached to the separate server instance.
Also check that you have not started two processes on the same port or attached Socket.IO to a server that is never started. Node reports network errors through socket error events; see the Node.js networking and error code documentation.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Rank #2
Make sure the client is targeting the right machine
In browser code, localhost means the computer running the browser. A phone, another computer, or a deployed frontend cannot use its own localhost to reach your development machine. Use a reachable IP address or hostname instead.
For a container or remote deployment, the server often needs to bind to an externally reachable interface, for example httpServer.listen(3000, "0.0.0.0"). That is a server bind address, not a client URL: clients must connect to a real hostname or IP. With Docker, confirm the port is published, such as docker run -p 3000:3000 your-image, and inspect docker ps, docker logs, and docker port.
On some systems, localhost resolves to IPv6 while the server is bound only to IPv4, or the reverse. Compare 127.0.0.1 and [::1] when diagnosing a local-only mismatch.
Check the URL, path, and namespace separately
Three values that often get confused are the server origin, the Socket.IO HTTP path, and the namespace:
Free tools Windows power users keep installed
One-click scans. No signup required.
- Origin: The protocol, host, and port, such as
https://api.example.com. - Path: The Engine.IO endpoint, normally
/socket.io/. If customized, client and server must agree, unless a proxy intentionally rewrites it. - Namespace: A Socket.IO logical connection such as
/admin, handled by the server separately from the HTTP path.
// Custom HTTP path: configure this same path on the server
const socket = io("https://api.example.com", {
path: "/realtime/socket.io/",
});
const io = new Server(httpServer, {
path: "/realtime/socket.io/",
});
// /admin is a namespace, not the HTTP path
const adminSocket = io("https://api.example.com/admin");
io.of("/admin").on("connection", (socket) => {
console.log("admin client connected");
});
A page served over HTTPS should connect to the HTTPS Socket.IO origin. An insecure HTTP connection from a secure page may be blocked as mixed content. For URL and path behavior, see client initialization and client options.
Configure CORS on the Socket.IO server
When a browser frontend at http://localhost:5173 connects to a backend at http://localhost:3000, those are different origins. Configure the Socket.IO server with the frontend’s exact origin:
const io = new Server(httpServer, {
cors: {
origin: "http://localhost:5173",
methods: ["GET", "POST"],
credentials: true,
},
});
Origins include scheme, host, and port. For example, localhost and 127.0.0.1 differ; so do HTTP and HTTPS or two different ports. Use an origin without a trailing slash.
Rank #3
If credentials are enabled, set a specific origin rather than *. For a production allowlist:
Recommended Free Tools
const allowedOrigins = new Set([
"https://app.example.com",
"https://www.example.com",
]);
const io = new Server(httpServer, {
cors: {
origin: (origin, callback) => {
if (!origin || allowedOrigins.has(origin)) {
callback(null, true);
} else {
callback(new Error("Origin not allowed"));
}
},
credentials: true,
},
});
Test the response header with the same origin the browser uses:
curl -i
-H "Origin: http://localhost:5173"
"http://localhost:3000/socket.io/?EIO=4&transport=polling"
Look for Access-Control-Allow-Origin matching that origin. If credentials are in use, check for Access-Control-Allow-Credentials: true as well. Configuring CORS only for ordinary Express routes does not necessarily configure Socket.IO’s handshake endpoint. CORS is enforced by browsers; it is not authentication. See the Socket.IO CORS guide.
Verify compatible Socket.IO versions and protocol
Socket.IO is not a plain WebSocket server. Its client speaks the Socket.IO protocol on top of Engine.IO, so a browser’s built-in new WebSocket() is not a substitute:
// Not a Socket.IO client
const ws = new WebSocket("ws://localhost:3000");
// Use the Socket.IO client package
import { io } from "socket.io-client";
const socket = io("http://localhost:3000");
Likewise, a Socket.IO client cannot connect directly to an arbitrary plain WebSocket server. Setting transports: ["websocket"] changes the transport; it does not change the protocol.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteCheck what is actually installed:
npm ls socket.io socket.io-client engine.io engine.io-client
Compatible combinations exist across some major versions, but not every combination works. Keeping socket.io and socket.io-client on the same major version is the simplest choice for a current JavaScript project:
npm install socket.io@4 socket.io-client@4
Do not blindly upgrade a legacy deployment whose other clients or server cannot move with it. A compatibility option such as allowEIO3 can help during a migration, but it is not a replacement for a planned version alignment. The Socket.IO troubleshooting guide covers compatibility errors.
Rank #4
Separate polling failures from WebSocket upgrade failures
Socket.IO commonly opens an Engine.IO session over HTTP polling and then attempts to upgrade to WebSocket. In browser DevTools, open Network, filter for socket.io, and inspect both the polling request and any WebSocket request. Check the URL, status, response body, CORS headers, and whether the WebSocket upgrade returns 101 Switching Protocols.
If polling works but the upgrade fails, investigate the reverse proxy, TLS termination, firewall, and platform support for WebSocket. A typical Nginx location looks like this:
location /socket.io/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Do not copy this blindly: Nginx’s proxy_pass path behavior depends on the location and whether the upstream should retain or replace the prefix. Verify that the public request reaches the intended Socket.IO endpoint. The reverse-proxy guide has additional configuration details.
Other deployment checks include whether TLS is terminated correctly, whether the proxy forwards the upgrade headers, whether /socket.io/ is routed to Node rather than the frontend, and whether an idle timeout closes connections unexpectedly. A CDN or ingress controller may also rewrite, cache, or block the endpoint.
As a diagnostic only, force polling to see whether the rest of the connection path works:
const socket = io("https://api.example.com", {
transports: ["polling"],
});
If this connects while the normal upgrade fails, focus on WebSocket support. The default transports are usually preferable during diagnosis because they retain fallback behavior. Forcing WebSocket can be appropriate in a deployment that supports it, but removes fallback and will not fix a wrong URL, server outage, CORS, or authentication rejection. See transport options and the Engine.IO transport description.
Check authentication and namespace middleware
A server may be reachable and still reject the connection during middleware. For example:
io.use((socket, next) => {
const token = socket.handshake.auth?.token;
if (!token) {
return next(new Error("authentication error"));
}
next();
});
The client must send the expected data:
const socket = io("http://localhost:3000", {
auth: { token: "example-token" },
});
socket.on("connect_error", (err) => {
console.log(err.message);
});
Log the rejection path on the server, but do not log real tokens or cookies in production. Express middleware handles normal HTTP routes; it does not automatically authorize Socket.IO connections. Namespace middleware may also reject one namespace while another is available. A connection that succeeds but whose later events are denied has reached application-level authorization, a different stage than the initial handshake.
Investigate multi-instance deployments
In a multi-instance deployment, polling requests belonging to one Engine.IO session may land on different Node processes. Without session affinity, the first request can succeed and a later request can fail with an unknown-session error or a 400. Intermittent failures that appear only after scaling are a strong reason to inspect load-balancer routing.
Check that polling sessions have sticky routing where required, that every instance uses compatible Socket.IO and Engine.IO versions, and that the load balancer forwards any affinity cookie or header. A shared adapter is needed for cross-process broadcasts, but it does not by itself make requests for a polling session reach the correct process. WebSocket-only transport may change the routing requirements, but it is not a universal solution for scaling or shared application state. See Socket.IO’s multiple-node guidance.
Use this diagnostic sequence
- Capture the actual failure. Save the full
connect_error, requested URL, HTTP status, response body, and whether the failed request used polling or WebSocket. - Confirm the listener. Check the Node logs and listening port. Verify that the Socket.IO instance is attached to the server that calls
listen(). - Test the handshake. Run
curl -i "http://HOST:PORT/socket.io/?EIO=4&transport=polling"from the server or a reachable machine. - Inspect the browser requests. In DevTools, compare polling and WebSocket requests, including status, headers, origin, and response.
- Compare both configurations. Check origin, path, namespace, protocol versions, authentication data, and HTTP/HTTPS scheme.
- Reduce variables. Test on one machine with the default path and transports, no proxy, and no authentication middleware. Add deployment components back one at a time.
This sequence distinguishes a transport or network problem from a server-side rejection far faster than changing random client options.
Frequently Asked Questions
Why does Socket.IO work locally but fail in production?
Production often adds a reverse proxy, TLS termination, a different public path, a firewall, container networking, or multiple backend instances. Inspect the public polling and WebSocket requests in DevTools and verify that the proxy routes them to the Socket.IO server with the expected path and upgrade support.
Does Express CORS middleware configure Socket.IO?
Not necessarily. Configure CORS on the Socket.IO server options for its handshake endpoint; Express CORS settings for regular routes do not automatically guarantee the required Socket.IO response headers.
Why does `localhost` fail from my phone or another computer?
In the browser, `localhost` refers to the device running the browser. Use a hostname or IP address reachable from that device, and ensure the server is bound and network ports are exposed appropriately.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsQuick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

