You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

远程服务器Docker环境下Socket.IO的Nginx/Traefik配置问题

Socket.IO连接失败排查(React+NestJS+Docker+Traefik)

本地非Docker环境下,React客户端(基于Vite)与NestJS Gateway的Socket.IO连接正常,但部署到预生产服务器后连接失败,确认问题出在Nginx或Traefik配置环节。


1. 后端NestJS配置

  • Dockerfile已暴露Socket网关端口8889:
EXPOSE 8889
  • WebSocket网关核心配置(ws.gateway.ts):
@WebSocketGateway(8889, { cors: { origin: "*" }, path: "/socket", namespace: "/api/frontend" })
export class EventsGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
  @WebSocketServer()
  server: Server;

  afterInit(server: Server) {
    console.log("Websocket initialized.");
  }

  handleDisconnect(client: Socket) {
    console.log(`Client disconnected: ${client.id}`);
  }

  handleConnection(client: Socket, ...args: any[]) {
    console.log(`Client connected: ${client.id}`);
  }

}

该网关类已接入NestJS模块系统。


2. 前端React/Vite配置

  • vite.config.mjs代理设置:
export default defineConfig({
   ...
   server: {
    host: "127.0.0.1",
    port: 3000,
    proxy: {
      "/api": {
        target: "http://127.0.0.1:8888/",   // 后端API代理地址
        changeOrigin: true,
        secure: false,
        ws: true,
        rewrite: path => path.replace(/^\/api/, ""),
      },
      "/socket": {
        target: "http://127.0.0.1:8889/",   // Socket连接代理地址
        changeOrigin: true,
        secure: false,
        ws: true,
        // rewrite: path => path.replace(/^\/socket/, ""),
      },
    },
  },
  ...
})
  • Socket连接代码(socket.tsx):
const WSClient: React.FC<WSClientProps> = props => {
   const uri = `${import.meta.env.VITE_APP_WS}/api/frontend`; // 实际连接地址:https://stage.mycompany.io/api/frontend

   useEffect(() => {
      console.log("Attempting to open socket to:", uri);
      const options: Partial<ManagerOptions & SocketOptions> = {
        reconnection: true,
        reconnectionDelay: 100,
        reconnectionAttempts: 3,
        path: "/socket",
      };
      const newSocket = io(uri, options);
      setSocket(newSocket);
      newSocket.on("connect", () => {
        console.log("Socket connected to backend server. Socket id:", newSocket.id, "on uri:", uri);
      });
      newSocket.on("disconnect", () => {
        console.log(`Socket id ${newSocket.id} has disconnected.`);
      });
      newSocket.io.on("reconnect_attempt", attempt => {
        console.log(`Socket reconnect_attempt ${attempt}.`); 
        if (attempt > 2) {
          console.log("Failed to disconnect the socket.");
        }
      });
   }, []);

   ...
}

3. Nginx配置(default.conf)

server {
    listen       80;
    server_name  localhost;
    root   /usr/share/nginx/html;
    index  index.html index.htm;
    resolver 127.0.0.11 ipv6=off valid=10s;
    access_log  /etc/nginx/conf.d/access.log;
    error_log  /etc/nginx/conf.d/errors.log debug;

    location @index {
        root   /usr/share/nginx/html;
        add_header Cache-Control 'private, no-cache, no-store, must-revalidate';
        add_header Expires 'Sat, 01 Jan 2000 00:00:00 GMT';
        add_header Pragma no-cache;
        try_files $uri $uri/ /index.html;
    }

    ##### SETUP FOR SOCKET.IO ######
    location /socket {
      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_pass http://127.0.0.1:8889/;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
    }
    #### END SETUP FOR SOCKET.IO ######

    location / {
        try_files $uri @index;
    }

    error_page   500 502 503 504  /50x.html;

    location = /50x.html {
        root   /usr/share/nginx/html;
    }

}

4. Docker相关配置

后端Dockerfile

FROM node:18.19-alpine3.18 as builder

ARG NPM_TOKEN
ARG PACKAGE_VERSION

RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc

RUN for i in $(seq 1 3); \
  do [ $i -gt 1 ] && sleep 5; \
  npm cache clean --force; \
  npm add @mycompany/mycompany-backend@$PACKAGE_VERSION --global && s=0 && break || s=$?; \
  done; (exit $s)

FROM node:18.19-alpine3.18

ARG PACKAGE_VERSION
ARG ENVIRONMENT

COPY --from=builder /usr/local/lib/node_modules/@mycompany/mycompany-backend /app/

WORKDIR /app/

EXPOSE 8889

ENTRYPOINT ["npm"]

CMD ["start"]

前端Dockerfile

FROM node:18.19-alpine3.18 as builder
ARG NPM_TOKEN
ARG PACKAGE_VERSION

RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc

RUN for i in $(seq 1 3); \
  do [ $i -gt 1 ] && sleep 5; \
  npm cache clean --force; \
  npm add @mycompany/mycompany-frontend@$PACKAGE_VERSION --global && s=0 && break || s=$?; \
  done; (exit $s)

FROM nginx:1.25.3-alpine
RUN rm -f /etc/nginx/conf.d/default.conf
RUN rm -f /etc/nginx/default.conf
COPY /conf/default.conf /etc/nginx/conf.d/default.conf

COPY --from=builder /usr/local/lib/node_modules/@mycompany/mycompany-frontend/dist/ /usr/share/nginx/html/

Docker Compose(base.yml)

version: '3.4'
networks:
  traefik-net:
  app-net:
volumes:
  certs:
services:
  proxy:
    image: traefik:1.7.5
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.role == manager
    environment:
      CF_API_EMAIL: "${CLOUDFLARE_EMAIL}"
      CF_API_KEY: "${CLOUDFLARE_API_KEY}"
    command: [
      "--loglevel=ERROR",
      "--web.address=:8080",
      "--retry",
      "--docker.swarmmode=true",
      "--docker.watch=true",
      "--docker.endpoint=unix:///var/run/docker.sock",
      "--defaultentrypoints=http,https",
      "--entryPoints=Name:http Address::80 Redirect.EntryPoint:https",
      "--entryPoints=Name:https Address::443 TLS",
      "--acme.dnschallenge.provider=cloudflare",
      "--acme.dnschallenge.delaybeforecheck=30",
      "--acme.acmelogging=true",
      "--acme.email=dave@mycompany.com",
      "--acme.entrypoint=https",
      "--acme.onhostrule=true",
      "--acme.storage=/certs/acme.json"
    ]
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - certs:/certs/
    networks:
      - traefik-net
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
      - "9229:9229"
      - "8889:8889"
  server:
    image: mycompany/mycompany-frontend:$MYCOMPANY_FRONTEND
    deploy:
      replicas: 1
      update_config:
        delay: 20s
        failure_action: rollback
      restart_policy:
        condition: any
      labels:
        traefik.backend: 'server'
        traefik.docker.network: 'mycompany_traefik-net'
        traefik.enable: 'true'
        traefik.port: '80'
    networks:
      - traefik-net
  api:
    image: mycompany/mycompany-backend:$MYCOMPANY_BACKEND
    deploy:
      replicas: 1
      update_config:
        delay: 20s
        failure_action: rollback
      restart_policy:
        condition: any
      labels:
        traefik.backend: 'api'
        traefik.docker.network: 'mycompany_traefik-net'
        traefik.enable: 'true'
        traefik.port: '8888'
    networks:
      - traefik-net
      - app-net
    dns:
      - 1.1.1.1
      - 8.8.8.8
      - 8.8.4.4

问题现状及求助

客户端React页面及API通信均正常,但Socket.IO连接失败,控制台显示尝试连接https://stage.mycompany.io/api/frontend。怀疑Nginx的proxy_pass配置错误(后端在独立Docker容器,localhost无法访问),尝试多种配置无效。目前推测Traefik仅配置了后端8888端口,未处理Socket的8889端口,需要正确配置Traefik以支持Socket.IO连接。

内容的提问来源于stack exchange,提问作者Mystify

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 14:22:03