WebSockets keep a connection open between browser and server for real-time features: chat, live dashboards, notifications, multiplayer games. In production, run them over TLS (wss://), usually with Nginx in front to handle certificates and route traffic.

Architecture

Browser ──wss://──▶ Nginx :443 (TLS) ──ws://──▶ app on 127.0.0.1:8080

Nginx terminates TLS and upgrades the connection; your app only listens on localhost.

1. A minimal WebSocket server (Node.js)

mkdir -p /opt/ws && cd /opt/ws
npm init -y && npm install ws

server.js:

import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ host: '127.0.0.1', port: 8080 });

wss.on('connection', (ws, req) => {
  const ip = req.headers['x-forwarded-for'] ?? req.socket.remoteAddress;
  ws.isAlive = true;
  ws.on('pong', () => (ws.isAlive = true));
  ws.on('message', (data) => {
    // broadcast to everyone
    for (const client of wss.clients) if (client.readyState === 1) client.send(data.toString());
  });
});

// Drop dead connections every 30 s
setInterval(() => {
  for (const ws of wss.clients) {
    if (!ws.isAlive) { ws.terminate(); continue; }
    ws.isAlive = false;
    ws.ping();
  }
}, 30_000);

Add "type": "module" to package.json. Python (websockets), Go (gorilla/websocket, nhooyr/websocket) and others follow the same pattern.

2. Run it as a service

/etc/systemd/system/ws.service:

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

[Service]
User=wsapp
WorkingDirectory=/opt/ws
ExecStart=/usr/bin/node server.js
Restart=always
LimitNOFILE=65535
Environment=NODE_ENV=production

[Install]
WantedBy=multi-user.target
adduser --system --group wsapp && chown -R wsapp: /opt/ws
systemctl enable --now ws

LimitNOFILE matters: every open connection uses a file descriptor.

3. Nginx with TLS

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    server_name ws.example.com;
    listen 443 ssl;
    http2 on;
    # ssl_certificate lines added by certbot

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_read_timeout 3600s;   # idle connections stay open
        proxy_send_timeout 3600s;
    }
}
certbot --nginx -d ws.example.com
nginx -t && systemctl reload nginx

Test from your computer:

npx wscat -c wss://ws.example.com

4. Security

  • Authenticate on connect. Pass a short-lived token (e.g. in the query string or a cookie) and verify it in the connection handler; close unauthenticated sockets immediately.

  • Check the Origin header for browser clients to prevent cross-site WebSocket hijacking.

  • Validate every message — size, format and rate. Set maxPayload in ws to reject huge frames.

  • Rate-limit connections per IP in Nginx:

    limit_conn_zone $binary_remote_addr zone=wsconn:10m;
    # in location:
    limit_conn wsconn 20;
  • Keep the app bound to 127.0.0.1 and firewall everything except 80/443.

5. Scaling

  • Connections per server: a small VPS can hold tens of thousands of idle WebSocket connections; CPU usage depends on message volume. Raise worker_connections in Nginx and file limits in systemd.
  • Multiple instances: run several app processes and use Nginx upstream with ip_hash (or a shared pub/sub like Redis) so broadcasts reach clients on every instance.
  • Latency: host close to your users — USA, Hong Kong or Tokyo. For global audiences, regional servers sharing state via Redis or a message queue reduce round trips.

Behind Cloudflare

Cloudflare proxies WebSockets on all plans. Keep the idle timeout in mind (connections without traffic may be closed after a period of inactivity) and send pings regularly. See VPS + Cloudflare.

Summary

Run the WebSocket app on localhost, put Nginx in front for TLS and upgrades, raise timeouts and file limits, authenticate every connection and validate every message. Related: Nginx reverse proxy for Docker and Node.js on a VPS.

  • WebSocket
  • Nginx
  • Node.js