Skip to content

Reverse Proxy ​

Hub services are typically exposed behind an Nginx reverse proxy that routes requests by path prefix. The hub-deployment repository includes a ready-to-use nginx.conf.

Routing ​

All services are accessible on a single port (default: 3000):

PathServiceWebSocket
/UI (frontend)No
/core/Core APIYes (Socket.io)
/auth/AuthupNo
/storage/StorageNo
/telemetry/TelemetryNo
/messenger/MessengerYes (Socket.io)

Nginx Configuration ​

nginx
worker_processes 1;
events { worker_connections 1024; }

http {
    sendfile on;
    client_max_body_size 0;        # Unlimited upload size
    chunked_transfer_encoding on;

    server {
        listen 3000;

        # Frontend
        location / {
            proxy_pass http://ui:3000/;
            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;
        }

        # Core API with WebSocket support
        location /core/ {
            rewrite ^/core/(.*) /$1 break;
            proxy_pass http://core:3000;
            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;
        }

        # Authentication
        location /auth/ {
            rewrite ^/auth/(.*) /$1 break;
            proxy_pass http://authup:3000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        # Storage
        location /storage/ {
            rewrite ^/storage/(.*) /$1 break;
            proxy_pass http://storage:3000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        # Telemetry
        location /telemetry/ {
            rewrite ^/telemetry/(.*) /$1 break;
            proxy_pass http://telemetry:3000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        # Messenger with WebSocket support
        location /messenger/ {
            rewrite ^/messenger/(.*) /$1 break;
            proxy_pass http://messenger:3000;
            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;
        }
    }
}

X-Forwarded-For must be set on every proxied location

proxy_set_header is not inherited from a sibling location, so each block above sets the forwarding headers itself. Without them the header reaches the service exactly as the client sent it — with the real peer address appended nowhere — and the client IP recorded on every audit event is entirely client-supplied.

Note this makes the true address present in the chain, not yet authoritative: hub currently reads the leftmost entry (getRequestIP(event, { trustProxy: true })), which a client can still prepend to. Tracked in #1868. If your ingress terminates client connections directly, proxy_set_header X-Forwarded-For $remote_addr (overwrite rather than append) closes that today.

TIP

For production, add SSL termination, rate limiting, and appropriate proxy_read_timeout values for long-running WebSocket connections.