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_upgradeandConnection "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:
| Setting | Default | Tuned Value | Location |
|---|---|---|---|
fs.file-max | 100000 | 500000 | /etc/sysctl.conf |
net.ipv4.ip_local_port_range | 32768–60999 | 10240–65000 | /etc/sysctl.conf |
net.core.somaxconn | 128 | 4096 | /etc/sysctl.conf |
| Nginx worker_connections | 768 | 10000 | /etc/nginx/nginx.conf |
| Nginx worker_processes | 1 | auto | /etc/nginx/nginx.conf |
| ulimit (open files) | 1024 | unlimited | websocket.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
You must be logged in to post a comment.