VPS WebSocket Server Setup: A Developer’s Guide to Real-Time Communication on Your Server

Real-time web applications — live chat, notifications, collaborative editing, financial dashboards — all depend on WebSocket for persistent, bidirectional communication between the browser and the server. Setting up WebSocket on a VPS requires more than just enabling a protocol: you need a proper reverse proxy, secure TLS termination, connection handling, and performance tuning. This guide covers the complete setup from scratch using Nginx and a Node.js WebSocket server on your VPS.

What Is WebSocket and Why Use It?

WebSocket (RFC 6455) provides a full-duplex communication channel over a single TCP connection. Unlike HTTP polling, which creates a new connection for each request-response cycle, WebSocket maintains a persistent connection. This means:

  • Lower latency: No HTTP handshake overhead for each message
  • Reduced bandwidth: Minimal framing overhead (2–14 bytes per message vs. HTTP headers)
  • Server push: The server can send data to the client without the client requesting it
  • Fewer connections: One TCP connection replaces hundreds of HTTP polling requests

For VPS users, WebSocket is especially valuable for real-time features without paying for managed services like Pusher or Firebase. With proper configuration, a single mid-range VPS can handle tens of thousands of concurrent WebSocket connections.

Prerequisites

  • A VPS with at least 2 GB RAM and a public IP address
  • Ubuntu 22.04 or 24.04 (commands assume apt-based system)
  • Nginx installed (sudo apt install nginx)
  • Node.js 18+ installed (sudo apt install nodejs npm)
  • A domain name pointing to your VPS IP (for TLS/SSL)

Step 1: Create a WebSocket Server with Node.js

We will use the ws library — the most popular and well-maintained WebSocket implementation for Node.js. Create a project directory and initialize:

mkdir ~/websocket-server
cd ~/websocket-server
npm init -y
npm install ws

Create server.js with a basic WebSocket server that handles connections, messages, and errors:

const { WebSocketServer } = require('ws');
const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('WebSocket server running\n');
});

const wss = new WebSocketServer({ server });

wss.on('connection', (ws, req) => {
  const clientIp = req.socket.remoteAddress;
  console.log(`Client connected: ${clientIp}`);

  // Send a welcome message
  ws.send(JSON.stringify({ type: 'welcome', message: 'Connected to WebSocket server' }));

  // Handle incoming messages
  ws.on('message', (data) => {
    console.log(`Received: ${data}`);
    // Broadcast to all connected clients
    wss.clients.forEach((client) => {
      if (client.readyState === ws.OPEN) {
        client.send(data.toString());
      }
    });
  });

  // Handle client disconnect
  ws.on('close', (code, reason) => {
    console.log(`Client disconnected: ${clientIp} (code: ${code})`);
  });

  // Handle errors
  ws.on('error', (err) => {
    console.error(`WebSocket error from ${clientIp}:`, err.message);
  });
});

const PORT = 3000;
server.listen(PORT, '127.0.0.1', () => {
  console.log(`WebSocket server listening on 127.0.0.1:${PORT}`);
});

Run it to test: node server.js. The server listens on 127.0.0.1:3000 — accessible only from localhost. We will expose it through Nginx in the next step.

Step 2: Configure Nginx as a WebSocket Reverse Proxy

Nginx supports WebSocket proxying natively since version 1.3. The key requirement is to set the Upgrade and Connection headers correctly. Create or edit your Nginx site configuration:

# /etc/nginx/sites-available/websocket
server {
    listen 80;
    server_name ws.example.com;

    # Redirect HTTP to HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name ws.example.com;

    ssl_certificate /etc/letsencrypt/live/ws.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ws.example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    location /ws/ {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        # WebSocket headers (critical)
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Forward client info
        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;

        # Timeouts for long-lived connections
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;

        # Buffer settings
        proxy_buffer_size 4k;
        proxy_buffering off;
    }
}

Critical directives explained:

  • proxy_set_header Upgrade $http_upgrade and Connection "upgrade": These two lines are what make WebSocket proxying work. Nginx forwards the client’s Upgrade request to the backend.
  • proxy_read_timeout 86400s: WebSocket connections are long-lived. Default Nginx timeout (60s) will kill idle connections. Set this to 24 hours or more.
  • proxy_buffering off: WebSocket is a streaming protocol — buffering would introduce latency.

Enable the site and test:

sudo ln -s /etc/nginx/sites-available/websocket /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Step 3: Run WebSocket Server as a Systemd Service

For production, run the Node.js server as a managed systemd service with automatic restart. Create /etc/systemd/system/websocket.service:

[Unit]
Description=WebSocket Server
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/websocket-server
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=5
Environment=NODE_ENV=production

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable websocket
sudo systemctl start websocket
sudo systemctl status websocket

Step 4: Security Considerations

WebSocket servers introduce unique security challenges compared to traditional HTTP APIs:

Authentication and Origin Validation

WebSocket does not have built-in authentication. Always validate the Origin header and implement a token-based handshake:

wss.on('connection', (ws, req) => {
  // Validate origin
  const origin = req.headers.origin;
  if (!origin || !origin.match(/^https?:\/\/(your-domain\.com|localhost)/)) {
    ws.close(4001, 'Unauthorized origin');
    return;
  }

  // Validate auth token from query string
  const params = new URLSearchParams(req.url.split('?')[1] || '');
  const token = params.get('token');
  if (!token || token !== process.env.WS_AUTH_TOKEN) {
    ws.close(4001, 'Invalid authentication token');
    return;
  }
  // ... rest of connection handler
});

Rate Limiting and Connection Throttling

Prevent resource exhaustion by limiting connections per IP and message frequency:

// Simple rate limiter
const rateLimits = new Map();
const MAX_CONNECTIONS_PER_IP = 10;
const MAX_MESSAGES_PER_SECOND = 30;

wss.on('connection', (ws, req) => {
  const ip = req.socket.remoteAddress;

  // Track connections per IP
  const count = Array.from(wss.clients).filter(
    c => c._socket && c._socket.remoteAddress === ip
  ).length;
  if (count > MAX_CONNECTIONS_PER_IP) {
    ws.close(4003, 'Connection limit reached');
    return;
  }

  // Track message rate
  let messageTimestamps = [];
  ws.on('message', () => {
    const now = Date.now();
    messageTimestamps = messageTimestamps.filter(t => now - t = MAX_MESSAGES_PER_SECOND) {
      ws.close(4004, 'Rate limit exceeded');
      return;
    }
    messageTimestamps.push(now);
  });
});

TLS Is Mandatory

Most browsers require secure WebSocket (wss://) for mixed content. Obtain a free Let’s Encrypt certificate:

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d ws.example.com

Performance Tuning for WebSocket on VPS

A mid-range VPS can handle 10,000–50,000 concurrent WebSocket connections with proper tuning:

SettingDefaultTuned ValueLocation
fs.file-max100000500000/etc/sysctl.conf
net.ipv4.ip_local_port_range32768–6099910240–65000/etc/sysctl.conf
net.core.somaxconn1284096/etc/sysctl.conf
Nginx worker_connections76810000/etc/nginx/nginx.conf
Nginx worker_processes1auto/etc/nginx/nginx.conf
ulimit (open files)1024unlimitedwebsocket.service

Apply sysctl settings:

cat >> /etc/sysctl.conf << 'EOF'
fs.file-max = 500000
net.ipv4.ip_local_port_range = 10240 65000
net.core.somaxconn = 4096
EOF
sysctl -p

Add the LimitNOFILE=infinity directive to the [Service] section of your systemd unit file and reload.

Testing Your WebSocket Server

From your browser’s developer console:

const ws = new WebSocket('wss://ws.example.com/ws/');

ws.onopen = () => console.log('Connected');
ws.onmessage = (event) => console.log('Received:', event.data);
ws.onclose = (event) => console.log('Disconnected:', event.code, event.reason);

ws.send(JSON.stringify({ type: 'chat', message: 'Hello from browser!' }));

Or using wscat from the command line:

# Install wscat
sudo npm install -g wscat

# Connect to your server
wscat -c wss://ws.example.com/ws/
Connected (press CTRL+C to quit)
> { "type": "ping" }
< { "type": "welcome", "message": "Connected to WebSocket server" }

Conclusion

Setting up a WebSocket server on your VPS is straightforward with Nginx as a reverse proxy and Node.js as the backend. The key takeaways: always use TLS (wss://), validate origins and tokens for security, set appropriate Nginx timeouts for long-lived connections, and tune kernel parameters for high concurrency. With these steps, your VPS can serve real-time features to thousands of concurrent users without needing a third-party service.

If you are looking for a VPS with good network throughput for real-time applications, compare VPS plans that offer unmetered bandwidth and low-latency interconnect between instances.

Leave a Reply