Storm docs logo
Search the docs.../
Explore Storm Products

Nginx SSL Passthrough Guide - Storm Streaming Server

To improve WebSocket/HTTP performance, an NGINX passthrough is recommended for the Storm Streaming Server. This guide covers SSL termination, WebSocket proxying, and CORS configuration.

Storm Server Configuration

In order for the passthrough to work, set HTTP/WebSocket ports in the server config to a local port (e.g. 8080) and disable internal SSL — NGINX will handle SSL termination instead. Example:

config/preferences.xml - VHost configuration
Code iconxml
<VHostSettings>
    <VHost host="127.0.0.1" port="8080" isSSL="false" isControlPanel="true">
        <Protocols>HTTP, WEBSOCKETS</Protocols>
    </VHost>
</VHostSettings>

All connections will go through NGINX and its SSL layer first, then packets will be forwarded to the Storm server over the local loopback interface.

NGINX Configuration

Below you'll find a sample NGINX configuration with SSL termination, WebSocket support, CORS handling, and REST API proxy support.

/etc/nginx/sites-available/mydomain.com
Code iconnginx
# Upstream - internal Storm server (non-SSL, loopback only)
upstream storm {
    server 127.0.0.1:8080;
}

# HTTP -> HTTPS redirection
server {
    listen 0.0.0.0:80;
    server_name mydomain.com;

    access_log /var/log/nginx/mydomain.com-access.log combined;
    error_log /var/log/nginx/mydomain.com-error.log error;

    add_header Strict-Transport-Security max-age=31536000 always;
    return 301 https://mydomain.com$request_uri;
}

# HTTPS server
server {
    listen 0.0.0.0:443 ssl;
    server_name mydomain.com;
    http2 on;

    access_log /var/log/nginx/ssl_mydomain.com-access.log combined;
    error_log /var/log/nginx/ssl_mydomain.com-error.log error;

    # Enforce HTTPS via HSTS
    add_header Strict-Transport-Security max-age=31536000 always;

    # Optional: limit concurrent connections per IP
    # limit_conn conn_limit_per_ip 10;

    # Block non-standard HTTP methods
    if ($request_method !~ ^(GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS)$ ) {
        return 444;
    }

    # --- SSL Settings ---
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/ssl/certs/mydomain.com.crt;
    ssl_certificate /etc/ssl/certs/mydomain.com.crt;
    ssl_certificate_key /etc/ssl/private/mydomain.com.key;

    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_session_ticket_key /etc/ssl/private/ticket.key;

    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:
                ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:
                DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:
                DHE-RSA-CHACHA20-POLY1305;
    ssl_prefer_server_ciphers off;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;

    # --- Main Location ---
    location / {

        # Hide CORS headers from upstream to avoid duplicates
        proxy_hide_header 'Access-Control-Allow-Origin';
        proxy_hide_header 'Access-Control-Allow-Methods';
        proxy_hide_header 'Access-Control-Allow-Headers';
        proxy_hide_header 'Access-Control-Expose-Headers';

        # CORS headers
        add_header 'Access-Control-Allow-Origin' '*' always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-Api-Key' always;
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;

        # Handle preflight requests (OPTIONS)
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-Api-Key';
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain; charset=utf-8';
            add_header 'Content-Length' 0;
            return 204;
        }

        # Proxy to Storm server
        proxy_pass http://storm;

        # Proxy headers
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Accept $http_accept;
        proxy_set_header Authorization $http_authorization;
        proxy_set_header User-Agent $http_user_agent;
        proxy_set_header X-Api-Key $http_x_api_key;

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

        # Don't rewrite Location headers
        proxy_redirect off;
    }

    # Protect sensitive paths
    location ~ /\.ht { deny all; }
    location /cron/   { deny all; }
    location ~ /\.svn { deny all; }
    location ~ /\.git { deny all; }
}

CORS Configuration

The configuration above includes full CORS (Cross-Origin Resource Sharing) support, which is required when your web player or REST API client is served from a different domain than the Storm server.

The key elements of the CORS setup are:

The proxy_hide_header directives strip any CORS headers that Storm might send, preventing duplicate headers in the response. NGINX then adds its own consistent set of CORS headers via add_header with the always flag, ensuring they are present on all response codes (including errors).

For preflight requests (HTTP OPTIONS method), NGINX returns a 204 No Content response directly without proxying to Storm. The Access-Control-Max-Age header is set to 1728000 seconds (20 days), which tells browsers to cache the preflight result and avoid repeated OPTIONS requests.

Security Note

The example above uses Access-Control-Allow-Origin '*' which allows requests from any domain. For production environments, consider restricting this to specific domains, e.g. add_header 'Access-Control-Allow-Origin' 'https://yourdomain.com' always;.

Listening on Multiple IPs

If your server has multiple network interfaces (e.g. a public IP and an internal network IP), you can add multiple listen directives:

Multiple listen directives
Code iconnginx
server {
    listen 192.168.1.10:80;
    listen 10.0.0.5:80;
    server_name mydomain.com;
    # ...
}

server {
    listen 192.168.1.10:443 ssl;
    listen 10.0.0.5:443 ssl;
    server_name mydomain.com;
    http2 on;
    # ...
}

This is common in cloud environments (e.g. GCP, AWS) where the instance has both a private and a public-facing network interface.

Blog
Support
About us
Patents
Term of use
Privacy policy
Contact
©2026 Storm Streaming Media. All Rights Reserved.