Reverse Proxy and Cloudflare Tunnel Setup

This guide covers routing for soundspan, including Listen Together Socket.IO traffic. It applies to any split frontend/backend deployment model (Kubernetes individual mode, docker-compose with separate frontend + backend services, or manual container setups).

Important for pre-published frontend images:

Why this matters

When frontend and backend are separate services, Socket.IO traffic still has to reach backend somehow. soundspan now handles this by default through the frontend runtime proxy.

Listen Together uses:

By default, requests to this path can terminate on frontend, which forwards them to backend. Listen Together preflights this route and blocks create/join with an explicit routing error banner when it is misconfigured.

Required routing

Default (recommended): Route all traffic to frontend

For split frontend/backend deployments:

Optional: Direct backend path-split routing

Use this when you want backend-direct API paths with pre-published frontend images (no custom frontend rebuild):

This route split is the runtime-safe replacement for trying to set NEXT_PUBLIC_API_URL / NEXT_PUBLIC_API_PATH_MODE on a prebuilt image. If you do want true in-app direct mode, publish a custom frontend image built with the desired NEXT_PUBLIC_* values.

For single-service deployments (all-in-one image, Helm deploymentMode: aio):

OIDC callback routing

First select the app/API layout in the OIDC_SSO.md deployment topology matrix. The matrix states when OIDC_WEB_BASE_URL is required. Cross-site app/API deployments on different registrable domains are not supported.

Set OIDC_REDIRECT_URI to the public URL that users open. Register the same exact value at the identity provider:

OIDC_REDIRECT_URI=https://soundspan.example.com/api/auth/oidc/callback

The callback path must reach the backend. The default examples below send /api/* through the frontend proxy. The direct path-split examples send /api/* straight to the backend. Both patterns deliver /api/auth/oidc/callback correctly.

Keep the public scheme, host, port, and path identical in soundspan and the identity provider. Do not use a container-only hostname in OIDC_REDIRECT_URI.

When the callback origin differs from the web origin but remains same-site, set the canonical web origin:

OIDC_REDIRECT_URI=https://api.example.com/api/auth/oidc/callback
OIDC_WEB_BASE_URL=https://music.example.com

Set SECURE_COOKIES=true when the browser uses HTTPS, including when Cloudflare or another edge terminates TLS.

NGINX example (Docker/Kubernetes edge)

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

    # Everything -> frontend (frontend proxies Listen Together socket path)
    location / {
        proxy_pass http://soundspan-frontend:3030;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Optional direct path-split variant:

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

    location /api/ {
        proxy_pass http://soundspan-backend:3006;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /socket.io/listen-together {
        proxy_pass http://soundspan-backend:3006;
        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-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
    }

    location / {
        proxy_pass http://soundspan-frontend:3030;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

If your reverse proxy already fronts a Helm ingress/gateway, you can point it at the ingress entrypoint and let Helm path rules handle any optional direct socket split.

Caddy example

soundspan.example.com {
    reverse_proxy soundspan-frontend:3030
}

Optional direct path-split variant:

soundspan.example.com {
    @api path /api/*
    reverse_proxy @api soundspan-backend:3006

    @listenTogether path /socket.io/listen-together*
    reverse_proxy @listenTogether soundspan-backend:3006

    reverse_proxy soundspan-frontend:3030
}

Cloudflare Tunnel example (cloudflared)

cloudflared supports websocket transport. For default mode, route hostname to frontend.

ingress:
  - hostname: soundspan.example.com
    service: http://soundspan-frontend.soundspan.svc.cluster.local:3030
  - service: http_status:404

Optional direct path-split variant (regex path matcher):

ingress:
  - hostname: soundspan.example.com
    path: /socket.io/listen-together.*
    service: http://soundspan-backend.soundspan.svc.cluster.local:3006
  - hostname: soundspan.example.com
    service: http://soundspan-frontend.soundspan.svc.cluster.local:3030
  - service: http_status:404

Notes:

Verification checklist

  1. Open browser devtools Network tab.
  2. Visit Listen Together page and join/create a session.
  3. Confirm socket handshake requests use /socket.io/listen-together.
  4. Confirm requests return success and websocket upgrade is established.
  5. Confirm UI state changes from Connecting... to connected (and no routing error banner appears).
  6. Optional direct probe (when polling fallback is enabled): open /socket.io/listen-together/?EIO=4&transport=polling in browser.
  7. If using default websocket-only policy (LISTEN_TOGETHER_ALLOW_POLLING=false), validate by confirming a successful websocket upgrade in browser devtools instead of polling probe.

Common failure signs


See also