The Complete NGINX on Ubuntu Series: Part 12 – WebSocket Support and Real-time Applications

The Complete NGINX on Ubuntu Series: Part 12 – WebSocket Support and Real-time Applications

Welcome to Part 12 of our comprehensive NGINX on Ubuntu series! We’ll implement WebSocket support and configure NGINX for real-time applications like chat systems, live updates, and bidirectional communication.

WebSocket Fundamentals

WebSockets provide full-duplex communication channels over a single TCP connection, enabling real-time data exchange between clients and servers without the overhead of HTTP polling.

graph TD
    A[Client Browser] --> B[NGINX WebSocket Proxy]
    B --> C[WebSocket Server]
    
  
    
    style B fill:#e1f5fe
   
graph TD
   
    D[WebSocket Features] --> E[Real-time Communication]
    D --> F[Low Latency]
    D --> G[Bidirectional Data]
    D --> H[Persistent Connection]
    
  
    style D fill:#e8f5e8
   
graph TD
   
    
    I[Use Cases] --> J[Chat Applications]
    I --> K[Live Updates]
    I --> L[Gaming]
    I --> M[Collaborative Editing]
    I --> N[Financial Tickers]
 
    
    
    style I fill:#fff3e0
graph TD
   
    
    O[NGINX WebSocket Proxy] --> P[Connection Upgrade]
    O --> Q[Header Forwarding]
    O --> R[Load Balancing]
    O --> S[SSL Termination]
    
    style O fill:#e3f2fd

Basic WebSocket Configuration

# Create WebSocket proxy configuration
sudo nano /etc/nginx/sites-available/websocket.example.com
upstream websocket_backend {
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
    
    # WebSocket connections are stateful, use ip_hash for sticky sessions
    ip_hash;
}

server {
    listen 80;
    server_name websocket.example.com;
    
    # Regular HTTP traffic
    location / {
        proxy_pass http://websocket_backend;
        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;
    }
    
    # WebSocket endpoint
    location /ws {
        proxy_pass http://websocket_backend;
        
        # WebSocket specific headers
        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;
        
        # WebSocket timeouts
        proxy_connect_timeout 7d;
        proxy_send_timeout 7d;
        proxy_read_timeout 7d;
        
        # Disable buffering for real-time communication
        proxy_buffering off;
        
        # Optional: Add WebSocket headers for debugging
        add_header X-WebSocket-Support "enabled" always;
    }
}

Advanced WebSocket Configuration

# Create advanced WebSocket configuration with multiple endpoints
sudo nano /etc/nginx/sites-available/advanced-websocket.example.com
# Map to handle different WebSocket protocols
map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

# Different backends for different services
upstream chat_websocket {
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
    ip_hash;
}

upstream notifications_websocket {
    server 127.0.0.1:4000;
    server 127.0.0.1:4001;
    ip_hash;
}

upstream game_websocket {
    server 127.0.0.1:5000;
    server 127.0.0.1:5001;
    ip_hash;
}

server {
    listen 80;
    server_name advanced-websocket.example.com;
    
    # Rate limiting for WebSocket connections
    limit_conn_zone $binary_remote_addr zone=ws_conn:10m;
    limit_req_zone $binary_remote_addr zone=ws_req:10m rate=10r/s;
    
    # Main application
    location / {
        proxy_pass http://chat_websocket;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    
    # Chat WebSocket with authentication
    location /ws/chat {
        # Rate limiting for WebSocket connections
        limit_conn ws_conn 10;
        limit_req zone=ws_req burst=20 nodelay;
        
        # Authentication check (optional)
        # auth_request /auth;
        
        proxy_pass http://chat_websocket/ws;
        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-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        
        # Long-lived connection settings
        proxy_connect_timeout 1h;
        proxy_send_timeout 1h;
        proxy_read_timeout 1h;
        
        # WebSocket specific optimizations
        proxy_buffering off;
        proxy_cache off;
        
        # Custom headers for application
        proxy_set_header X-WebSocket-Protocol $http_sec_websocket_protocol;
        proxy_set_header X-WebSocket-Version $http_sec_websocket_version;
        
        # Logging for WebSocket connections
        access_log /var/log/nginx/websocket-chat.log combined;
        
        add_header X-WebSocket-Service "chat" always;
    }
    
    # Notifications WebSocket
    location /ws/notifications {
        limit_conn ws_conn 5;
        limit_req zone=ws_req burst=10 nodelay;
        
        proxy_pass http://notifications_websocket/ws;
        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-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        
        proxy_connect_timeout 30m;
        proxy_send_timeout 30m;
        proxy_read_timeout 30m;
        
        proxy_buffering off;
        
        access_log /var/log/nginx/websocket-notifications.log combined;
        
        add_header X-WebSocket-Service "notifications" always;
    }
    
    # Gaming WebSocket with higher performance settings
    location /ws/game {
        limit_conn ws_conn 20;
        limit_req zone=ws_req burst=50 nodelay;
        
        proxy_pass http://game_websocket/ws;
        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-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        
        # Shorter timeouts for gaming
        proxy_connect_timeout 10s;
        proxy_send_timeout 10s;
        proxy_read_timeout 1h;
        
        proxy_buffering off;
        
        access_log /var/log/nginx/websocket-game.log combined;
        
        add_header X-WebSocket-Service "game" always;
    }
    
    # Health check endpoint
    location /health {
        access_log off;
        return 200 "WebSocket proxy healthy\n";
        add_header Content-Type text/plain;
    }
}

SSL/TLS WebSocket Configuration

# Create secure WebSocket configuration
sudo nano /etc/nginx/sites-available/secure-websocket.example.com
server {
    listen 443 ssl http2;
    server_name secure-websocket.example.com;
    
    # SSL Configuration
    ssl_certificate /etc/letsencrypt/live/secure-websocket.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/secure-websocket.example.com/privkey.pem;
    
    # SSL Security Settings
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    
    # Security Headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    
    # Main application
    location / {
        proxy_pass http://websocket_backend;
        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;
    }
    
    # Secure WebSocket endpoint (wss://)
    location /ws {
        proxy_pass http://websocket_backend/ws;
        
        # WebSocket headers
        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-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # SSL-specific headers
        proxy_set_header X-Forwarded-SSL on;
        proxy_set_header X-Forwarded-Port 443;
        
        # WebSocket timeouts for secure connections
        proxy_connect_timeout 1h;
        proxy_send_timeout 1h;
        proxy_read_timeout 1h;
        
        proxy_buffering off;
        
        # Enhanced logging for secure WebSockets
        access_log /var/log/nginx/secure-websocket.log combined;
        
        add_header X-WebSocket-Secure "enabled" always;
    }
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name secure-websocket.example.com;
    return 301 https://$server_name$request_uri;
}

WebSocket Load Balancing

graph TD
    A[Client WebSocket] --> B[NGINX Load Balancer]
    B --> C[WebSocket Server 1]
    B --> D[WebSocket Server 2]
    B --> E[WebSocket Server 3]
   
    
    style B fill:#e1f5fe
  
graph TD
  
    
    F[Load Balancing Methods] --> G[IP Hash - Sticky Sessions]
    F --> H[Least Connections]
    F --> I[Round Robin with Session]
  
    
   
    style F fill:#e8f5e8
   
graph TD
   
    J[Session Management] --> K[Sticky Sessions]
    J --> L[Shared State Store]
    J --> M[Stateless Design]
    
  
    style J fill:#fff3e0
  
graph TD
  
    
    N[Scaling Strategies] --> O[Horizontal Scaling]
    N --> P[Redis Pub/Sub]
    N --> Q[Message Queues]
    
  
    style N fill:#e3f2fd
# Create WebSocket load balancing configuration
sudo nano /etc/nginx/sites-available/websocket-lb.example.com
# WebSocket servers with health checks
upstream websocket_cluster {
    # Use ip_hash for sticky sessions (stateful WebSockets)
    ip_hash;
    
    server 192.168.1.10:3000 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:3000 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:3000 max_fails=3 fail_timeout=30s;
    server 192.168.1.13:3000 backup;
    
    keepalive 32;
}

# Alternative: Stateless WebSocket cluster with shared storage
upstream websocket_stateless {
    least_conn;
    
    server 192.168.1.20:3000 max_fails=2 fail_timeout=20s;
    server 192.168.1.21:3000 max_fails=2 fail_timeout=20s;
    server 192.168.1.22:3000 max_fails=2 fail_timeout=20s;
    
    keepalive 32;
}

server {
    listen 80;
    server_name websocket-lb.example.com;
    
    # Connection limits for WebSocket
    limit_conn_zone $binary_remote_addr zone=ws_conn_limit:10m;
    limit_conn ws_conn_limit 50;
    
    # Main application
    location / {
        proxy_pass http://websocket_cluster;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    
    # Stateful WebSocket with sticky sessions
    location /ws/stateful {
        proxy_pass http://websocket_cluster/ws;
        
        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-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        
        # Add upstream server info for debugging
        add_header X-Upstream-Server $upstream_addr always;
        
        proxy_connect_timeout 1h;
        proxy_send_timeout 1h;
        proxy_read_timeout 1h;
        proxy_buffering off;
    }
    
    # Stateless WebSocket with shared state
    location /ws/stateless {
        proxy_pass http://websocket_stateless/ws;
        
        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-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        
        # Session identifier for stateless design
        proxy_set_header X-Session-ID $cookie_session_id;
        
        add_header X-Upstream-Server $upstream_addr always;
        
        proxy_connect_timeout 30s;
        proxy_send_timeout 30s;
        proxy_read_timeout 1h;
        proxy_buffering off;
    }
}

WebSocket Monitoring and Debugging

# Create WebSocket monitoring script
sudo nano /usr/local/bin/websocket-monitor.sh
#!/bin/bash

# WebSocket Connection Monitor
ACCESS_LOG="/var/log/nginx/access.log"
WS_LOG="/var/log/nginx/websocket.log"

monitor_websockets() {
    echo "=== WebSocket Connection Monitor ==="
    echo "Generated: $(date)"
    echo
    
    # Current WebSocket connections
    echo "--- Active WebSocket Connections ---"
    local ws_connections=$(ss -tun | grep ':3000\|:4000\|:5000' | grep ESTAB | wc -l)
    echo "WebSocket backend connections: $ws_connections"
    
    # NGINX WebSocket proxy connections
    local nginx_connections=$(ss -tun | grep ':80\|:443' | grep ESTAB | wc -l)
    echo "NGINX proxy connections: $nginx_connections"
    echo
    
    # WebSocket upgrade requests (last hour)
    echo "--- WebSocket Upgrades (Last Hour) ---"
    local hour_ago=$(date -d '1 hour ago' '+%d/%b/%Y:%H')
    local current_hour=$(date '+%d/%b/%Y:%H')
    
    local upgrades=$(grep -E "$hour_ago|$current_hour" "$ACCESS_LOG" 2>/dev/null | grep -i "upgrade.*websocket" | wc -l)
    echo "WebSocket upgrade requests: $upgrades"
    
    # WebSocket endpoints usage
    echo "--- WebSocket Endpoint Usage ---"
    grep -E "$hour_ago|$current_hour" "$ACCESS_LOG" 2>/dev/null | grep "/ws" | awk '{print $7}' | sort | uniq -c | sort -nr
    echo
    
    # Failed WebSocket connections
    echo "--- Failed WebSocket Connections ---"
    local failed_ws=$(grep -E "$hour_ago|$current_hour" "$ACCESS_LOG" 2>/dev/null | grep "/ws" | grep -E " 4[0-9][0-9] | 5[0-9][0-9] " | wc -l)
    echo "Failed WebSocket requests: $failed_ws"
    echo
    
    # Top WebSocket clients
    echo "--- Top WebSocket Clients ---"
    grep -E "$hour_ago|$current_hour" "$ACCESS_LOG" 2>/dev/null | grep "/ws" | awk '{print $1}' | sort | uniq -c | sort -nr | head -5
    echo
}

check_websocket_health() {
    echo "=== WebSocket Health Check ==="
    echo
    
    # Test WebSocket endpoints
    local endpoints=(
        "ws://websocket.example.com/ws"
        "ws://advanced-websocket.example.com/ws/chat"
        "ws://websocket-lb.example.com/ws/stateful"
    )
    
    for endpoint in "${endpoints[@]}"; do
        echo "Testing: $endpoint"
        
        # Use websocat or curl to test WebSocket connection
        if command -v websocat >/dev/null 2>&1; then
            timeout 5s websocat --exit-on-eof "$endpoint" <<< "ping" >/dev/null 2>&1
            if [ $? -eq 0 ]; then
                echo "✅ $endpoint - Connected successfully"
            else
                echo "❌ $endpoint - Connection failed"
            fi
        else
            # Fallback to HTTP upgrade test
            local host=$(echo "$endpoint" | sed 's|ws://||' | sed 's|/.*||')
            local path=$(echo "$endpoint" | sed 's|ws://[^/]*||')
            
            curl -s -H "Upgrade: websocket" -H "Connection: Upgrade" \
                 -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
                 -H "Sec-WebSocket-Version: 13" \
                 "http://$host$path" >/dev/null 2>&1
            
            if [ $? -eq 0 ]; then
                echo "✅ $endpoint - HTTP upgrade successful"
            else
                echo "❌ $endpoint - HTTP upgrade failed"
            fi
        fi
    done
    echo
}

case "${1:-monitor}" in
    monitor)
        monitor_websockets
        ;;
    health)
        check_websocket_health
        ;;
    watch)
        while true; do
            clear
            monitor_websockets
            echo "Press Ctrl+C to exit..."
            sleep 10
        done
        ;;
    *)
        echo "Usage: $0 {monitor|health|watch}"
        ;;
esac

# Make executable: sudo chmod +x /usr/local/bin/websocket-monitor.sh

Sample WebSocket Application

Here’s a simple WebSocket server example. For the complete application code, check our GitHub repository: WebSocket Chat Application

# Quick WebSocket server setup (Node.js example)
# Install dependencies
npm init -y
npm install ws express

# Create simple WebSocket server (server.js)
const WebSocket = require('ws');
const express = require('express');
const http = require('http');

const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server, path: '/ws' });

// Serve static files
app.use(express.static('public'));

// WebSocket connection handling
wss.on('connection', (ws, request) => {
    console.log('New WebSocket connection from:', request.socket.remoteAddress);
    
    ws.on('message', (message) => {
        console.log('Received:', message.toString());
        
        // Broadcast to all connected clients
        wss.clients.forEach((client) => {
            if (client.readyState === WebSocket.OPEN) {
                client.send(message);
            }
        });
    });
    
    ws.on('close', () => {
        console.log('WebSocket connection closed');
    });
});

server.listen(3000, () => {
    console.log('WebSocket server running on port 3000');
});

Complete Application Files: The full WebSocket chat application with HTML client, advanced features, and deployment scripts is available in our GitHub Gist collection.

Testing WebSocket Configuration

# Enable WebSocket sites
sudo ln -s /etc/nginx/sites-available/websocket.example.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/advanced-websocket.example.com /etc/nginx/sites-enabled/

# Test configuration
sudo nginx -t

# Reload NGINX
sudo systemctl reload nginx

# Test WebSocket upgrade headers
curl -H "Upgrade: websocket" \
     -H "Connection: Upgrade" \
     -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
     -H "Sec-WebSocket-Version: 13" \
     -H "Host: websocket.example.com" \
     http://localhost/ws -v

# Monitor WebSocket connections
/usr/local/bin/websocket-monitor.sh

# Check WebSocket health
/usr/local/bin/websocket-monitor.sh health

# Watch WebSocket traffic in real-time
sudo tail -f /var/log/nginx/websocket-chat.log

# Test with websocat (install with: cargo install websocat)
# echo "Hello WebSocket" | websocat ws://websocket.example.com/ws

What’s Next?

Excellent! You’ve successfully configured NGINX for WebSocket support and real-time applications. Your server can now handle bidirectional communication, load balance WebSocket connections, and support modern real-time web applications.

Coming up in Part 13: NGINX Microservices Architecture and API Gateway

References


This is Part 12 of our 22-part NGINX series. Your server now supports real-time WebSocket applications! Next, we’ll build a microservices API gateway. Questions? Share them in the comments!

Written by:

472 Posts

View All Posts
Follow Me :