I have been trying to hours so I am resorting to asking here so I can take a break. I have successfully set up my Node.js application with NGINX and self signed certificate for now.
Here is my NGINX configuration:
server {
listen 80;
server_name domain.org;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl default_server;
server_name api-dev.domain.org;
ssl_certificate /etc/ssl/certs/selfsigned.crt;
ssl_certificate_key /etc/ssl/private/selfsigned.key;
location /socket.io/ {
proxy_pass http://server:3001/socket.io/;
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-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 75s;
proxy_read_timeout 86400;
proxy_send_timeout 86400;
proxy_set_header Connection '';
chunked_transfer_encoding off;
proxy_cache off;
proxy_redirect off;
client_max_body_size 100M;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
}
location / {
proxy_pass http://server:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
I use this locally / on localhost which does work:
server {
listen 80 default_server;
location /socket.io/ {
proxy_pass http://server:3001/socket.io/;
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-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
proxy_send_timeout 86400;
}
location / {
proxy_pass http://server:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
But I get this in the live environment:
ERR_UNKNOWN_URL_SCHEME
And this in the console logs:
websocket.js:119 WebSocket connection to 'wss://api-dev.domain.org:3001/socket.io/?token=eyJhbGciOiJIUzI1NiIsInR…3Nn0.I4R7BaDenABYRueephc6w3lCFgw8AFScU_NRjPK5KFA&EIO=4&transport=websocket' failed:
useWebSocket.tsx:32 [WebSocket] Connection error: TransportError: websocket error at WS.onError (transport.js:39:1) at ws.onerror (websocket.js:49:1)
I have verified the port is open:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4e6871b2ef40 project-nginx "/docker-entrypoint.…" 40 minutes ago Up 39 minutes 0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp project-nginx-1
0e4650ffc45d project-server "docker-entrypoint.s…" 40 minutes ago Up 39 minutes 0.0.0.0:3001->3001/tcp, :::3001->3001/tcp project-server-1
cf1279a5d068 postgres "docker-entrypoint.s…" 40 minutes ago Up 40 minutes (healthy) 0.0.0.0:5432->5432/tcp, :::5432->5432/tcp project-postgres-1
1f5c2424c2ca docker.elastic.co/elasticsearch/elasticsearch:7.10.2 "/tini -- /usr/local…" 40 minutes ago Up 40 minutes 9200/tcp, 9300/tcp project-es02-1
e1a7e4ab6121 docker.elastic.co/elasticsearch/elasticsearch:7.10.2 "/tini -- /usr/local…" 40 minutes ago Up 40 minutes 9200/tcp, 9300/tcp project-es03-1
f8cb20b8f51f redis "docker-entrypoint.s…" 40 minutes ago Up 40 minutes (healthy) 0.0.0.0:6379->6379/tcp, :::6379->6379/tcp project-redis-1
2eaf48bb4c7d docker.elastic.co/elasticsearch/elasticsearch:7.10.2 "/tini -- /usr/local…" 40 minutes ago Up 40 minutes 0.0.0.0:9200->9200/tcp, :::9200->9200/tcp, 0.0.0.0:9300->9300/tcp, :::9300->9300/tcp project-es01-1
ss -tulnp | grep :3001
tcp LISTEN 0 4096 0.0.0.0:3001 0.0.0.0:*
tcp LISTEN 0 4096 [::]:3001 [::]:*
I am using this on the frontend to connect (which, again, works locally):
import { useEffect, useState } from 'react';
import { io, Socket } from 'socket.io-client';
import { getWSSURL } from 'shared/api/config';
import { Events } from 'shared/constants';
import { MessageDTO } from 'shared/api/types';
export const useWebSocket = (token: string | null, messages: MessageDTO[], setMessages: React.Dispatch<React.SetStateAction<MessageDTO[]>>) => {
const [isConnected, setIsConnected] = useState(false);
const [socket, setSocket] = useState<Socket | null>(null);
useEffect(() => {
if (!token) {
console.warn('[WebSocket] No token provided, skipping connection');
return;
}
console.log('[WebSocket] Connecting to', getWSSURL());
const newSocket: Socket = io(getWSSURL(), {
transports: ['websocket', 'polling'],
query: { token },
withCredentials: true,
rejectUnauthorized: false
});
newSocket.on('connect', () => {
console.log('[WebSocket] Connected successfully');
setIsConnected(true);
});
newSocket.on('connect_error', (error) => {
console.error('[WebSocket] Connection error:', error);
setIsConnected(false);
});
newSocket.on('disconnect', (reason) => {
console.warn('[WebSocket] Disconnected:', reason);
});
newSocket.on('error', (error) => {
console.error('[WebSocket] Socket error:', error);
});
setSocket(newSocket);
return () => {
console.log('[WebSocket] Disconnecting...');
setIsConnected(false);
newSocket.disconnect();
};
}, [token]);
const joinChannel = (channelID: number) => {
if (!socket || !isConnected) {
console.warn('[WebSocket] Cannot join channel, socket not initialized or not connected');
return;
}
console.log(`[WebSocket] Joining channel ${channelID}`);
socket.emit('joinChannel', { channelID });
socket.off('joinedChannel');
socket.on('joinedChannel', (data) => {
if (data.channelID === channelID && data.success) {
console.log(`[WebSocket] Successfully joined channel ${channelID}`);
}
});
socket.off(Events.MESSAGE_CREATE);
socket.on(Events.MESSAGE_CREATE, (newMessage: MessageDTO) => {
console.log(`[WebSocket] Received message in channel ${channelID}:`, newMessage);
setMessages((prevMessages) => [...prevMessages, newMessage]);
});
};
const leaveChannel = (channelID: number) => {
if (!socket) {
console.warn('[WebSocket] Cannot leave channel, socket not initialized');
return;
}
console.log(`[WebSocket] Leaving channel ${channelID}`);
socket.emit('leaveChannel', { channelID });
};
return { socket, messages, joinChannel, leaveChannel };
};
Here is the server setup on Nestjs:
import { singletonLogger } from '@/common/services/logger/logger.service';
import { OnGatewayConnection, OnGatewayDisconnect, SubscribeMessage, WebSocketGateway, WebSocketServer } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { Events } from '@/utils/Constants';
@WebSocketGateway(Number(process.env.WSS_PORT ?? '3001'), {
cors: {
origin: '*',
methods: ['GET', 'POST'],
credentials: true
},
transports: ['websocket', 'polling'],
allowEIO3: true
})
export class EventsGateway implements OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer()
server!: Server;
afterInit(_: Server) {
singletonLogger.debug(`WebSocket server initialized on port ${process.env.WSS_PORT ?? '3001'}`);
}
handleConnection(client: Socket) {
console.log(`Client connected: ${client.id}`);
}
handleDisconnect(client: Socket) {
console.log(`Client disconnected: ${client.id}`);
}
@SubscribeMessage('joinChannel')
handleJoinChannel(client: Socket, data: { channelID: number }) {
client.join(`channel_${data.channelID}`);
console.log(`Client ${client.id} joined channel_${data.channelID}`);
client.emit('joinedChannel', { channelID: data.channelID, success: true });
}
@SubscribeMessage('leaveChannel')
handleLeaveChannel(client: Socket, data: { channelID: number }) {
client.leave(`channel_${data.channelID}`);
console.log(`Client ${client.id} left channel_${data.channelID}`);
}
emitMessageToChannel(channelID: number, message: any) {
this.server.to(`channel_${channelID}`).emit(Events.MESSAGE_CREATE, message);
}
}
Any insight appreciated, thanks!
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
These answers are provided by our Community. If you find them useful, show some love by clicking the heart. If you run into issues leave a comment, or add your own answer to help others.
Hi there,
The setup looks good! The main issue is likely this part of your frontend error:
That shows your client is trying to connect directly to port 3001, bypassing NGINX.
But since NGINX is listening on 443, not 3001, the WebSocket connection fails.
To fix this, make sure your
getWSSURL()
points towss://api-dev.domain.org/socket.io/
(no port!), so the WebSocket goes through NGINX, which proxies to port 3001 internally.If you hardcoded the port somewhere in
getWSSURL()
, remove it and just let it default towss://your-domain/socket.io
.- Bobby