Reverse Proxy and Load Balancing

Sockeon is designed to work seamlessly behind reverse proxies (nginx, Apache, HAProxy) and load balancers. This guide covers configuration, security considerations, and best practices.

Overview

When your Sockeon server sits behind a reverse proxy, the server only sees the proxy's IP address and connection details, not the real client information. Sockeon's trust proxy feature allows you to safely extract the real client information from proxy headers.

Why Use a Reverse Proxy?

  • SSL/TLS Termination - Handle HTTPS at the proxy level
  • Load Balancing - Distribute traffic across multiple server instances
  • Security - Add additional security layers (firewall, DDoS protection)
  • Caching - Cache static content at the proxy level
  • Compression - Compress responses at the proxy level
  • Logging - Centralized logging and monitoring

Configuration

Basic Setup

use Sockeon\Sockeon\Config\ServerConfig;
use Sockeon\Sockeon\Connection\Server;

$config = new ServerConfig([
    'host' => '0.0.0.0',
    'port' => 6001,
    'trust_proxy' => [
        '127.0.0.1',        // Localhost
        '10.0.0.0/8',       // Private network
        '192.168.0.0/16',   // Private network
    ],
    'health_check_path' => '/health',
]);

$server = new Server($config);
$server->run();

Trust Proxy Settings

Trust All Proxies (Development Only)

// ⚠️ WARNING: Never use in production!
$config->setTrustProxy(true);
$config->setTrustProxy([
    '127.0.0.1',        // Localhost
    '10.0.0.5',         // Specific nginx server
    '10.0.0.6',         // Another nginx server
]);

Trust Network Ranges (CIDR)

$config->setTrustProxy([
    '10.0.0.0/8',       // All 10.x.x.x addresses
    '172.16.0.0/12',    // All 172.16-31.x.x addresses
    '192.168.0.0/16',   // All 192.168.x.x addresses
]);

Health Check Endpoint

Enable a health check endpoint for load balancers:

$config->setHealthCheckPath('/health');

The health check returns:

{
  "status": "healthy",
  "timestamp": 1234567890,
  "server": {
    "clients": 5,
    "uptime": 12345,
    "uptime_human": "3h 25m 45s"
  }
}

Proxy Headers

Sockeon automatically processes the following headers when trust proxy is enabled:

X-Forwarded-For

Contains the original client IP address.

X-Forwarded-For: 1.2.3.4
X-Forwarded-For: 1.2.3.4, 10.0.0.1  # Multiple proxies

X-Forwarded-Proto

Contains the original protocol (http or https).

X-Forwarded-Proto: https

X-Forwarded-Host

Contains the original hostname.

X-Forwarded-Host: api.example.com

X-Forwarded-Port

Contains the original port number.

X-Forwarded-Port: 443

Forwarded (RFC 7239)

Sockeon also supports the standard Forwarded header:

Forwarded: for=1.2.3.4;proto=https;host=api.example.com

Nginx Configuration

Basic Configuration

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://127.0.0.1:6001;
        proxy_http_version 1.1;

        # WebSocket support
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Forward proxy headers
        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;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port $server_port;

        # Timeouts for WebSocket
        proxy_connect_timeout 7d;
        proxy_send_timeout 7d;
        proxy_read_timeout 7d;
    }
}

HTTPS Configuration

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

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        proxy_pass http://127.0.0.1:6001;
        proxy_http_version 1.1;

        # WebSocket support
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Forward proxy headers
        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;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port $server_port;
    }
}

Apache Configuration

<VirtualHost *:443>
    ServerName api.example.com
    
    SSLEngine on
    SSLCertificateFile /path/to/cert.pem
    SSLCertificateKeyFile /path/to/key.pem

    <Location />
        ProxyPass http://127.0.0.1:6001/
        ProxyPassReverse http://127.0.0.1:6001/
        
        # WebSocket support
        RewriteEngine on
        RewriteCond %{HTTP:Upgrade} websocket [NC]
        RewriteCond %{HTTP:Connection} upgrade [NC]
        RewriteRule ^/?(.*) "ws://127.0.0.1:6001/$1" [P,L]
        
        # Forward proxy headers
        RequestHeader set X-Forwarded-Proto "https" env=HTTPS
        RequestHeader set X-Forwarded-Host %{HTTP_HOST}e
        RequestHeader set X-Forwarded-Port %{SERVER_PORT}e
        RequestHeader set X-Forwarded-For %{REMOTE_ADDR}e
    </Location>
</VirtualHost>

HAProxy Configuration

frontend websocket_frontend
    bind *:443 ssl crt /path/to/cert.pem
    mode http
    default_backend websocket_backend

backend websocket_backend
    mode http
    balance roundrobin
    option httpchk GET /health
    http-check expect status 200
    server ws1 127.0.0.1:6001 check
    server ws2 127.0.0.1:6002 check
    
    # Forward headers
    http-request set-header X-Forwarded-Proto https if { ssl_fc }
    http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
    http-request set-header X-Forwarded-Host %[req.hdr(Host)]
    http-request set-header X-Forwarded-Port %[dst_port]
    http-request set-header X-Forwarded-For %[src]

Security Considerations

Never Trust All Proxies in Production

// ❌ BAD - Security vulnerability
$config->setTrustProxy(true);

// ✅ GOOD - Specific trusted IPs
$config->setTrustProxy(['127.0.0.1', '10.0.0.0/8']);

Why It's Dangerous

If you trust all proxies, an attacker who can reach your server directly can send fake headers:

# Attacker sends request directly to server
curl -H "X-Forwarded-For: 1.1.1.1" \
     -H "X-Forwarded-Proto: https" \
     http://your-server:6001/api/info

# Server thinks request came from 1.1.1.1 via HTTPS
# But it actually came directly from attacker's IP

Best Practices

  1. Always use specific IPs or CIDR ranges in production
  2. Use private network ranges (10.x.x.x, 172.16-31.x.x, 192.168.x.x) for internal proxies
  3. Only trust proxies you control
  4. Test your configuration to ensure it works correctly
  5. Monitor for suspicious activity in your logs

Testing

Test Request Details

Create an endpoint to verify proxy headers are working:

#[HttpRoute('GET', '/api/request-details')]
public function getRequestDetails(Request $request): Response
{
    return Response::json([
        'ip_address' => $request->getIpAddress(),
        'scheme' => $request->getScheme(),
        'host' => $request->getHost(),
        'port' => $request->getPort(),
        'url' => $request->getUrl(),
        'headers' => $request->getHeaders(),
    ]);
}

Test Health Check

curl http://api.example.com/health

Expected response:

{
  "status": "healthy",
  "timestamp": 1234567890,
  "server": {
    "clients": 5,
    "uptime": 12345,
    "uptime_human": "3h 25m 15s"
  }
}

Troubleshooting

Problem: IP Address Shows Proxy IP

Solution: Ensure proxy IP is in trusted list:
$config->setTrustProxy(['10.0.0.5']); // Your proxy IP

Problem: Scheme Always Shows "http"

Solution: Ensure proxy sends X-Forwarded-Proto header:
proxy_set_header X-Forwarded-Proto $scheme;

Problem: Wrong URL in getUrl()

Solution: Ensure all proxy headers are set in nginx:
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;

Load Balancing

Multiple Server Instances

Run multiple Sockeon server instances on different ports:

# Server 1
php server.php --port=6001

# Server 2
php server.php --port=6002

# Server 3
php server.php --port=6003

Sticky Sessions

For WebSocket connections, use sticky sessions in your load balancer to ensure clients stay connected to the same server instance.

Nginx sticky sessions:
upstream sockeon_backend {
    ip_hash;  # Sticky sessions based on client IP
    server 127.0.0.1:6001;
    server 127.0.0.1:6002;
    server 127.0.0.1:6003;
}

server {
    location / {
        proxy_pass http://sockeon_backend;
        # ... proxy headers ...
    }
}

See Also