Troubleshooting connection issues
The
Admin UI
can give you additional insights about the status of your Socket.IO deployment.
Common/known issues:
Other common gotchas:
Possible explanations:
As explained in the
"What Socket.IO is not"
section, the Socket.IO client is not a WebSocket implementation and thus will not be able to establish a connection with a WebSocket server, even with
transports: ["websocket"]
:
const socket = io("ws://echo.websocket.org", {
transports: ["websocket"]
});
Please make sure the Socket.IO server is actually reachable at the given URL. You can test it with:
curl "<the server URL>/socket.io/?EIO=4&transport=polling"
which should return something like this:
0{"sid":"Lbo5JLzTotvW3g2LAAAA","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":20000}
If that's not the case, please check that the Socket.IO server is running, and that there is nothing in between that prevents the connection.
Note: v1/v2 servers (which implement the v3 of the protocol, hence the
EIO=3
) will return something like this:
96:0{"sid":"ptzi_578ycUci8WLB9G1","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":5000}2:40
Maintaining backward compatibility is a top priority for us, but in some particular cases we had to implement some breaking changes at the protocol level:
-
from v1.x to v2.0.0 (released in May 2017), to improve the compatibility with non-Javascript clients (see
here
)
-
from v2.x to v3.0.0 (released in November 2020), to fix some long-standing issues in the protocol once for all (see
here
)
v4.0.0
contains some breaking changes in the API of the JavaScript server. The Socket.IO protocol itself was not updated, so a v3 client will be able to reach a v4 server and vice-versa (see
here
).
For example, reaching a v3/v4 server with a v1/v2 client will result in the following response:
< HTTP/1.1 400 Bad Request
< Content-Type: application/json
{"code":5,"message":"Unsupported protocol version"}
Here is the compatibility table for the
JS client
:
JS Client version
|
Socket.IO server version
|
1.x
|
2.x
|
3.x
|
4.x
|
1.x
|
YES
|
NO
|
NO
|
NO
|
2.x
|
NO
|
YES
|
YES
1
|
YES
1
|
3.x
|
NO
|
NO
|
YES
|
YES
|
4.x
|
NO
|
NO
|
YES
|
YES
|
[1]
Yes, with
allowEIO3: true
Here is the compatibility table for the
Java client
:
Java Client version
|
Socket.IO server version
|
2.x
|
3.x
|
4.x
|
1.x
|
YES
|
YES
1
|
YES
1
|
2.x
|
NO
|
YES
|
YES
|
[1]
Yes, with
allowEIO3: true
Here is the compatibility table for the
Swift client
:
Swift Client version
|
Socket.IO server version
|
2.x
|
3.x
|
4.x
|
v15.x
|
YES
|
YES
1
|
YES
2
|
v16.x
|
YES
3
|
YES
|
YES
|
[1]
Yes, with
allowEIO3: true
(server) and
.connectParams(["EIO": "3"])
(client):
SocketManager(socketURL: URL(string:"http://localhost:8087/")!, config: [.connectParams(["EIO": "3"])])
[2]
Yes,
allowEIO3: true
(server)
[3]
Yes, with
.version(.two)
(client):
SocketManager(socketURL: URL(string:"http://localhost:8087/")!, config: [.version(.two)])
If you see the following error in your console:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ...
It probably means that:
Please see the documentation
here
.
When scaling to multiple Socket.IO servers, you need to make sure that all the requests of a given Socket.IO session reach the same Socket.IO server. The explanation can be found
here
.
Failure to do so will result in HTTP 400 responses with the code:
{"code":1,"message":"Session ID unknown"}
Please see the documentation
here
.
By default, the client sends — and the server expects — HTTP requests with the "/socket.io/" request path.
This can be controlled with the
path
option:
Server
import { Server } from "socket.io";
const io = new Server({
path: "/my-custom-path/"
});
io.listen(3000);
Client
import { io } from "socket.io-client";
const socket = io(SERVER_URL, {
path: "/my-custom-path/"
});
In that case, the HTTP requests will look like
<SERVER_URL>/my-custom-path/?EIO=4&transport=polling[&...]
.
import { io } from "socket.io-client";
const socket =
io("/my-custom-path/");
means the client will try to reach the
namespace
named "/my-custom-path/", but the request path will still be "/socket.io/".
First and foremost, please note that disconnections are common and expected, even on a stable Internet connection:
-
anything between the user and the Socket.IO server may encounter a temporary failure or be restarted
-
the server itself may be killed as part of an autoscaling policy
-
the user may lose connection or switch from WiFi to 4G, in case of a mobile browser
-
the browser itself may freeze an inactive tab
That being said, the Socket.IO client will always try to reconnect, unless specifically told
otherwise
.
Possible explanations for a disconnection:
When a browser tab is not in focus, some browsers (like
Chrome
) throttle JavaScript timers, which could lead to a disconnection by ping timeout
in Socket.IO v2
, as the heartbeat mechanism relied on
setTimeout
function on the client side.
As a workaround, you can increase the
pingTimeout
value on the server side:
const io = new Server({
pingTimeout: 60000
});
Please note that upgrading to Socket.IO v4 (at least
[email protected]
, due to
this
) should prevent this kind of issues, as the heartbeat mechanism has been reversed (the server now sends PING packets).
Since the format of the packets sent over the WebSocket transport is similar in v2 and v3/v4, you might be able to connect with an incompatible client (see
above
), but the connection will eventually be closed after a given delay.
So if you are experiencing a regular disconnection after 30 seconds (which was the sum of the values of
pingTimeout
and
pingInterval
in Socket.IO v2), this is certainly due to a version incompatibility.
If you get disconnected while sending a huge payload, this may mean that you have reached the
maxHttpBufferSize
value, which defaults to 1 MB. Please adjust it according to your needs:
const io = require("socket.io")(httpServer, {
maxHttpBufferSize: 1e8
});
A huge payload taking more time to upload than the value of the
pingTimeout
option can also trigger a disconnection (since the
heartbeat mechanism
fails during the upload). Please adjust it according to your needs:
const io = require("socket.io")(httpServer, {
pingTimeout: 60000
});
In most cases, you should see something like this:
-
the Engine.IO handshake (contains the session ID — here,
zBjrh...AAAK
— that is used in subsequent requests)
-
the Socket.IO handshake request (contains the value of the
auth
option)
-
the Socket.IO handshake response (contains the
Socket#id
)
-
the WebSocket connection
-
the first HTTP long-polling request, which is closed once the WebSocket connection is established
If you don't see a
HTTP 101 Switching Protocols
response for the 4th request, that means that something between the server and your browser is preventing the WebSocket connection.
Please note that this is not necessarily blocking since the connection is still established with HTTP long-polling, but it is less efficient.
You can get the name of the current transport with:
Client-side
socket.on("connect", () => {
const transport = socket.io.engine.transport.name;
socket.io.engine.on("upgrade", () => {
const upgradedTransport = socket.io.engine.transport.name;
});
});
Server-side
io.on("connection", (socket) => {
const transport = socket.conn.transport.name;
socket.conn.on("upgrade", () => {
const upgradedTransport = socket.conn.transport.name;
});
});
Possible explanations:
Please see the documentation
here
.
BAD:
io.on("connection", async (socket) => {
await longRunningOperation();
socket.on("hello", () => {
});
});
GOOD:
io.on("connection", async (socket) => {
socket.on("hello", () => {
});
await longRunningOperation();
});
Please note that, unless
connection state recovery
is enabled, the
id
attribute is an
ephemeral
ID that is not meant to be used in your application (or only for debugging purposes) because:
-
this ID is regenerated after each reconnection (for example when the WebSocket connection is severed, or when the user refreshes the page)
-
two different browser tabs will have two different IDs
-
there is no message queue stored for a given ID on the server (i.e. if the client is disconnected, the messages sent from the server to this ID are lost)
Please use a regular session ID instead (either sent in a cookie, or stored in the localStorage and sent in the
auth
payload).
See also:
Since most serverless platforms (such as Vercel) bill by the duration of the request handler, maintaining a long-running connection with Socket.IO (or even plain WebSocket) is not recommended.
References: