Content-Security-Policy when hosting livekit on a different domain than FLUXER_DOMAIN

(#586) Bug Fixed self-hosting voice

Summary

In my setup i have Fluxer installed on a local server and I forward traffic using netbird to my VPS and expose it using Netbirds reverse proxy feature to the internet. When trying to connect to a voice chat the console throws the error: ` Content-Security-Policy: The page’s settings blocked the loading of a resource (connect-src) at wss://livekit-tcp.mydomain.com:7881/rtc/v1 because it violates the following directive: “connect-src 'self' data: https://*.fluxer.app/ wss://.fluxer.app https://.fluxer.media wss://.fluxer.media https://fluxer-uploads.ewr1.vultrobjects.com/ https://hcaptcha.com/ https://.hcaptcha.com https://fluxerstatus.com/ https://fluxer.media/ http://127.0.0.1:21863/ http://127.0.0.1:21864/ https://chat.mydomain.com/ https://chat.mydomain.com/media%E2%80%9D ` The reason for this issue is that Netbird doesnt allow multiple services of different type (TCP, UDP, HTTPS) exposed on the same subdomain, thus I am forced to put LiveKit's TCP and UDP ports on different subdomains. From my understanding, the fluxer code hardcodes the allowed address for connections to be only the FLUXER_DOMAIN. This is obviously also a Netbird issue but nontheless could be fixed by allowing extra domains via a new environment variable.

Steps to reproduce

  1. Default installation of selfhosted Fluxer
  2. .env with changes:FLUXER_PUBLIC_SCHEME=httpsFLUXER_PUBLIC_PORT=443FLUXER_CADDY_SITE_ADDRESS=:80
  3. livekit.yaml changes:use_external_ip: falsenode_ip: ip_of_netbird_vps
  4. Configured Voice region and voice server in admin panel. Endpoint was set to: wss://livekit-tcp.proxy.mydomain.com:7881. Other domain combinations (with and without port, direct ip adress of vps, just mydomain.com etc.) were all tried with no success.
  5. Netbird reverse proxy exposes fluxer http service under subdomain chat.mydomain.com and 7881/tcp and 7882/udp are exposed under livekit-tcp.mydomain.com and livekit-udp.mydomain.com respectivly

Environment

OS: Ubuntu 26.04 LXC on Proxmox Browser: Zen Browser (Firefox) Device: Windows 11

Logs or screenshots

`{"level":"info","time":"2026-06-16T11:09:29.811Z","service":"fluxer-api","env":"production","method":"POST","path":"/internal/rpc","status":200,"durationMs":14,"msg":"Request completed"} {"level":"info","time":"2026-06-16T11:09:59.869Z","service":"fluxer-api","env":"production","method":"GET","path":"/.well-known/fluxer","status":200,"durationMs":3,"msg":"Request completed"} {"level":"error","time":"2026-06-16T11:10:00.413Z","service":"fluxer-api","env":"production","error":{"type":"TypeError","message":"fetch failed: connect ECONNREFUSED vps_ip_address:7881","stack":"TypeError: fetch failed\ncaused by: Error: connect ECONNREFUSED vps_ip_address:7881\n at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1705:16)"},"msg":"Error disconnecting LiveKit participant"} {"level":"info","time":"2026-06-16T11:10:00.413Z","service":"fluxer-api","env":"production","method":"POST","path":"/internal/rpc","status":200,"durationMs":39,"msg":"Request completed"}`

8 comments

Sign in with Fluxer to comment and vote.
Comment by @Buco7854
RexSystem 1 vote originally by @Buco7854 on GitHub
I am using external livekit can confirm the issue. Most probably fluxer doesnt have this issue cause of the wildcards but for a selfhosted instance adding "Voice Servers" via the admin UI the CSP breaks it. Please note that this issue has more to do with fluxer CSP directive for voice servers than Netbird. I am not using Netbird but my livekit server is still hosted on a different domain. (Not speciifc to this issue but the Turnstile challenge domain for example is never set in CSP, at least in V1 Docker image so Turnstile challenges also breaks) .
Comment by @shleeable
RexSystem 1 vote originally by @shleeable on GitHub
TBH. I had that same problem with netbird. I think there is a bug with their reverse proxy (it's very new)
Comment by @SleazeStiKs
RexSystem 1 vote originally by @SleazeStiKs on GitHub OP
Setting the voice server endpoint to wss://fluxer_domain/livekit seems to mitigate this issue. Atleast as long as both the FLUXER_DOMAIN and the subdomain used for exposing 7881/tcp & 7882/udp in Netbird, both resolve to the same IP adress.
Comment by @kerichdev
RexSystem 1 vote edited originally by @kerichdev on GitHub
@SleazeStiKs could you please elaborate? I'm trying to replicate your exact setup here. Did you end up exposing the LiveKit over a subdomain but set the server endpoint to the root domain?
Comment by @SleazeStiKs
RexSystem 1 vote originally by @SleazeStiKs on GitHub OP
Fluxer itself is exposed on the root domain in my case chat.mydomain.com, Livekit is exposed under livekit-tcp.mydomain.com and livekit-udp.mydomain.com. In the admin panel I set the endpoint to wss://livekit-tcp.mydomain.com:7881. After some trial and error i ended up setting the endpoint to chat.mydomain.com as that resolves to the same IP as livekit-tcp.mydomain.com. This seems to fix this particular error.
Comment by @kerichdev
RexSystem 1 vote originally by @kerichdev on GitHub
That's weird, I just seem to get a Server unreachable error. Hm... Edit: Fixed! The correct solution entirely is exposing the tcp and udp endpoints, and setting the final endpoint to wss://fluxer_root.your.domain/livekit
Comment by @MrRubberDucky
RexSystem 1 vote originally by @MrRubberDucky on GitHub
If anyone is looking for a temp fix until CSP gets re-adjusted / fixed up and has similar setup as mine where LiveKit runs on VPS with a separate domain then here's what I've done with Caddy running on my VPS. It just makes same livekit instance be reverse proxied on both https://chat.mydomain.tld/livekit* and https://lk.mydomain.tld which solves the problem for the time being.
https://chat.mydomain.tld {
  handle_path /livekit* {
    # Change this to ip:port that LiveKit listens on internally, I run my caddy inside a container.
    # This just means localhost pretty much in Podman lingo.
    reverse_proxy host.containers.internal:7880 {
      header_up Connection "upgrade"
      header_up Upgrade {http.request.header.Upgrade}
      header_up X-Real-IP {remote_host}
    }
  }
  handle {
    # WireGuard tunnel, change this to whatever ip:port you expose your internal HTTP Caddy instance with
    reverse_proxy 10.100.1.3:8777
  }
}
# ...and normal livekit domain just chilling here that Matrix uses.
https://lk.mydomain.tld {
  @jwt_service {
    path /sfu/get* /healthz* /get_token*
  }
  handle @jwt_service {
    reverse_proxy lk-jwt-service:8081 {
      header_up X-Real-IP {remote_host}
    }
  }
  reverse_proxy host.containers.internal:7880 {
    header_up Connection "upgrade"
    header_up Upgrade {http.request.header.Upgrade}
    header_up X-Real-IP {remote_host}
  }
}
This way my single LiveKit instance is still available on lk.mydomain.tld while also being accessible to Fluxer from wss://chat.mydomain.tld/livekit <-- this is the one you add from admin panel after doing this. Hope it helps somebody out.
Comment by @Buco7854
RexSystem 1 vote originally by @Buco7854 on GitHub
If anyone is looking for a temp fix until CSP gets re-adjusted / fixed up and has similar setup as mine where LiveKit runs on VPS with a separate domain then here's what I've done with Caddy running on my VPS. It just makes same livekit instance be reverse proxied on both https://chat.mydomain.tld/livekit* and https://lk.mydomain.tld which solves the problem for the time being.
https://chat.mydomain.tld {
  handle_path /livekit* {
    # Change this to ip:port that LiveKit listens on internally, I run my caddy inside a container.
    # This just means localhost pretty much in Podman lingo.
    reverse_proxy host.containers.internal:7880 {
      header_up Connection "upgrade"
      header_up Upgrade {http.request.header.Upgrade}
      header_up X-Real-IP {remote_host}
    }
  }
  handle {
    # WireGuard tunnel, change this to whatever ip:port you expose your internal HTTP Caddy instance with
    reverse_proxy 10.100.1.3:8777
  }
}
# ...and normal livekit domain just chilling here that Matrix uses.
https://lk.mydomain.tld {
  @jwt_service {
    path /sfu/get* /healthz* /get_token*
  }
  handle @jwt_service {
    reverse_proxy lk-jwt-service:8081 {
      header_up X-Real-IP {remote_host}
    }
  }
  reverse_proxy host.containers.internal:7880 {
    header_up Connection "upgrade"
    header_up Upgrade {http.request.header.Upgrade}
    header_up X-Real-IP {remote_host}
  }
}
This way my single LiveKit instance is still available on lk.mydomain.tld while also being accessible to Fluxer from wss://chat.mydomain.tld/livekit <-- this is the one you add from admin panel after doing this. Hope it helps somebody out.
You can do simpler just force caddy to add csp.
# The beginning of your file...
        handle {
                reverse_proxy app-proxy:8080 {
                        # Here add CSP for Turnstile and external Livekit
                        header_down Content-Security-Policy "script-src "  "script-src 'unsafe-eval' https://challenges.cloudflare.com "
                        header_down Content-Security-Policy "frame-src "   "frame-src https://challenges.cloudflare.com "
                        header_down Content-Security-Policy "connect-src " "connect-src https://lk.domain.com wss://lk.domain.com "
                }
        }
}
:8088 {
        handle_path /api/* {
                reverse_proxy api:8080
        }
Sorry for ident i'm on mobile. You should add those at the same level of staticAsset etc