I am developing an web app that utilize Socket.io for communication between client's browser and backend. For the environment, I ran both backend and frontend under single container, and it works without issue on local environment (both under docker and not under docket). BUT, when I deploy them on production that uses https, the socket.io doesn't work any more. For context :
backend setup
import { createServer } from 'http'
const app = express()
const server = createServer(app)
const allowedOrigins = [
'',
'http://my-frontendip:5177',
'http://localhost:5173',
'http://localhost',
'http://127.0.0.1:8080',
'http://127.0.0.1:8000',
'http://127.0.0.1:5713',
]
const io = new Server(server, {
transports: ['websocket'],
cors: {
origin: function (origin, callback) {
if (!origin) return callback(null, true); // Allow non-browser requests (e.g., Postman)
if (allowedOrigins.indexOf(origin) === -1) {
const msg = 'The CORS policy for this site does not allow access from the specified Origin.';
return callback(new Error(msg), false); // Pass error to callback
}
return callback(null, true);
},
credentials: true,
},
pingTimeout: 25000,
pingInterval: 60000
})
app.use(cors({
origin: function (origin, callback) {
if (!origin) return callback(null, true); // Allow non-browser requests (e.g., Postman)
if (allowedOrigins.indexOf(origin) === -1) {
const msg = 'The CORS policy for this site does not allow access from the specified Origin.';
return callback(new Error(msg), false); // Pass error to callback
}
return callback(null, true);
},
credentials: true,
}))
io.use((socket, next) => {
console.log('Socket Handshake Query:', socket.handshake.query);
console.log('Socket Handshake Auth:', socket.handshake.auth);
next();
});
io.use(Mid.socketToken)
mongoose.connect(process.env.MONGO_URI)
.then(() => {
socketCycle(io)
})
server.listen(PORT, '0.0.0.0', () => {
console.log(`Socket & Server is running on port ${PORT}`)
})
This is my socketCycle
export const socketCycle = (io) => {
io.on('connection', (socket) => {
console.log('A user connected', new Date().toISOString());
const connectionCount = io.sockets.sockets.size;
console.log(`Total connections: ${connectionCount}`, new Date().toISOString());
socket.on('greet', (data, callback) => {
console.log('greet', data)
callback({ status: 'hello' })
})
socket.on('room/join', (room, callback) => {
socket.join(room);
callback({ status: 'ok' });
console.log('Room joined:', room, new Date().toISOString());
})
socket.on('disconnect', () => {
console.log('A user disconnected', new Date().toISOString());
})
})
io.engine.on("connection_error", (err) => {
console.log("Header Socket", err.req.headers);
console.error('Connection Error:', err.code, err.message, err.context);
});
io.engine.on('initial_headers', (headers, req) => {
console.log('Initial Headers:', headers);
});
io.engine.on('headers', (headers, req) => {
console.log('Response Headers:', headers);
});
}
frontend
const newSocket = io(import.meta.env.VITE_SOCKET_URL, {
transports: ['websocket'],
withCredentials: true,
auth: { token },
secure: true,
});
Both end use socket.io(-client for frontend) version 4.8.1. And again, the app works fine when communicating over https. BUT the socket isn't. Somehow it the connection keep failing.
This is the log from io.engine.on('connection_error')
Header Socket {
host: 'localhost:8088',
'cf-ray': '90d768df3d1cfdbe-SIN',
'x-forwarded-for': '103.166.234.12, 172.70.208.141',
'cf-connecting-ip': '103.166.234.12',
'accept-encoding': 'gzip, br',
'x-forwarded-proto': 'https',
'sec-websocket-extensions': 'permessage-deflate; client_max_window_bits',
'cf-ipcountry': 'ID',
'cf-visitor': '{"scheme":"https"}',
'sec-websocket-key': 'BE1glAV94/gHYG4Ritwy/g==',
'sec-websocket-version': '13',
'cdn-loop': 'cloudflare; loops=1',
authorization: 'Bearer <jwt bearer>',
'x-forwarded-host': '<backend-host>',
'x-forwarded-server': '<backend-host>',
connection: 'Keep-Alive'
}
Connection Error: 3 Bad request { name: 'TRANSPORT_HANDSHAKE_ERROR' }
The only similar issue I found online is from socket.io's github issue. .io/issues/4916#issuecomment-1881453706 But I think the asker has different deployment environment from me. So I don't know where to go.
Other alternative I found is to run the socket on different port than the app itself, and then connect it via ws
. I have tried that and it works (via Postman), but if possible I want to prioritize using secure connection for this app, not to mention somehow I cannot force the frontend to use ws
instead of wss
, so it keep fail when trying to connect to socket connection because it ran on ws.
I hope my explanation is easy to understand, Thank you in advance
TL;DR Socket.io connection keep failing with error TRANSPORT_HANDSHAKE_ERROR in secure (tls/ssl) environment