Report this

What is the reason for this report?

Getting NGINX + Socket.IO to work

Posted on March 16, 2025

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!

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Start building today

From GPU-powered inference and Kubernetes to managed databases and storage, get everything you need to build, scale, and deploy intelligent applications.