Behind a reverse proxy
The control plane can sit behind a proxy. The stream never goes through it, so see Reaching Quasar remotely before expecting internet play.
On an owned install
Section titled “On an owned install”Point your existing proxy at the control plane’s HTTP port (8080 by default),
not 8443. The examples below use quasar.example.com and a control plane at
quasar.lan:8080; replace both with your addresses.
-
Pass the original
Hostand sendX-Forwarded-Proto. The signaling address is built from them; owned installs have noPUBLIC_BASE_URL. The examples below do this. -
Add the public URL to allowed origins in Admin ▸ Settings. Opening first-run setup through the public address saves it for you. There is no environment override on an owned install.
-
Set
QUASAR_TRUSTED_PROXIES(a seed input) to your proxy’s address or CIDR. Until it is set, first-run setup and sign-in through the proxy are refused. An owned install always serves HTTPS, so sign in on its HTTPS port (8443) meanwhile. To change it later, on the first machine:Terminal window docker exec -it quasar-recovery quasar-recovery reconfigure --yes QUASAR_TRUSTED_PROXIES=<address or CIDR>
Keep QUASAR_PUBLIC_HOST for direct LAN access on the HTTPS port.
Proxy examples
Section titled “Proxy examples”These add Quasar to an existing proxy; they do not install the proxy or configure its certificate. Keep your current certificate settings.
# Add to your existing Caddyfile. Caddy forwards WebSocket upgrades and# sets X-Forwarded-* on its own, so this stays short.quasar.example.com { reverse_proxy http://quasar.lan:8080 { # Long-lived: /v1/signal and /agent/ws stay open for the whole session. transport http { read_timeout 3600s write_timeout 3600s } header_up X-Forwarded-Proto https flush_interval -1 }}# Traefik file provider. Drop this in the directory your existing# providers.file.directory points at, then reload.## Container labels are the alternative, but they only work when Traefik can# reach the control plane's Docker network. A file provider pointed at# host:port works whatever your topology is.http: routers: quasar: rule: "Host(`quasar.example.com`)" service: quasar entryPoints: [websecure] tls: certResolver: your-resolver middlewares: [quasar-headers]
middlewares: quasar-headers: headers: customRequestHeaders: X-Forwarded-Proto: https
services: quasar: loadBalancer: # Covers /v1/signal and /agent/ws; Traefik proxies WebSockets natively. passHostHeader: true servers: - url: "http://quasar.lan:8080" responseForwarding: flushInterval: -1
# Entry point read timeouts live in your static config, not here. Quasar's# connections are long-lived, so make sure respondingTimeouts.readTimeout is 0# (no limit) or well above 60s, or sessions get cut.# Add to your existing nginx sites-enabled. TLS directives omitted:# keep whatever your other services already use.server { listen 443 ssl; http2 on; server_name quasar.example.com;
# ssl_certificate /path/to/fullchain.pem; # ssl_certificate_key /path/to/privkey.pem;
location / { proxy_pass http://quasar.lan:8080; proxy_http_version 1.1; 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 https; }
# Signaling and agent enrollment are WebSockets and stay open for the whole # session. Without the upgrade headers signaling breaks entirely; without # the timeouts sessions get cut after 60s. location ~ ^/(v1/signal|agent/ws)$ { proxy_pass http://quasar.lan:8080; 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-Proto https; proxy_buffering off; proxy_read_timeout 3600s; proxy_send_timeout 3600s; }}# NGINX Proxy Manager is GUI-driven, so there is no file to paste.# Create a Proxy Host with these values:## Domain Names quasar.example.com# Scheme http# Forward Hostname/IP quasar.lan# Forward Port 8080# Websockets Support ON <- signaling breaks without this# Block Common Exploits OFF <- it interferes with the WebSocket upgrade# SSL your certificate, Force SSL on## Then paste the block below into the Advanced tab. It covers the two paths# that stay open for a whole session, /v1/signal and /agent/ws: NPM's default# 60s read timeout cuts them otherwise.
location ~ ^/(v1/signal|agent/ws)$ { proxy_pass http://quasar.lan:8080; 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-Proto https; proxy_buffering off; proxy_read_timeout 3600s; proxy_send_timeout 3600s;}On a legacy Compose install
Section titled “On a legacy Compose install”The hardened overlay puts Caddy with ACME certificates in front and derives the URL
and origins from QUASAR_PUBLIC_HOST in deploy/.env:
docker compose -f deploy/docker-compose.yml \ -f deploy/docker-compose.hardened.yml up -dWith your own proxy, set these in deploy/.env.
PUBLIC_BASE_URL
Section titled “PUBLIC_BASE_URL”PUBLIC_BASE_URL=https://quasar.example.comNeeded when the proxy rewrites Host.
Allowed origins
Section titled “Allowed origins”QUASAR_ALLOWED_ORIGINS=https://quasar.example.comGates the signaling WebSocket. Unset uses the list in Admin ▸ Settings; set (even empty), it overrides that list. With neither, only same-origin is allowed.
- Comma-separated exact
scheme://host[:port]entries, no path, query or credentials. *is rejected. A malformed entry stops startup.- Keep your LAN origins (such as
https://<lan-ip>:8443) when adding a public one.
QUASAR_TRUSTED_PROXIES
Section titled “QUASAR_TRUSTED_PROXIES”The proxy’s address or network, and nothing broader. With the bundled Caddy, use
that Compose project’s bridge subnet. Empty is safe: forwarded headers are ignored.
That includes X-Forwarded-Proto, so first-run setup and sign-in through the proxy
are refused until this is set. A proxy that is not listed shows up in the control plane’s log as
forwarded-from-unlisted-peer with its address (once per 10 minutes). If that is
your reverse proxy, put its address here. The way round it depends on the stack:
- Default Compose stack: set up and sign in directly on the HTTPS port (8443).
- Hardened overlay (no published control-plane port) or
QUASAR_TLS=off(no HTTPS port): none.BOOTSTRAP_ADMIN_*creates an admin, but nobody can sign in until the proxy is named.
Proxy requirements
Section titled “Proxy requirements”- Forward WebSocket upgrades on
/v1/signaland/agent/ws. - No buffering on those paths.
- A long read timeout (a 60-second idle timeout cuts sessions).
- Send
X-Forwarded-Proto: https, set by the proxy, not passed through from the client.
What not to proxy
Section titled “What not to proxy”Point node agents on the same machine or trusted network at the control plane
directly (/agent/ws over HTTP), not through the proxy.
When your proxy already compresses responses, set QUASAR_COMPRESSION=false
on the control plane and let the proxy do it.
Checking it worked
Section titled “Checking it worked”- Open the console at the proxy address and run Admin’s access check.
- Connect through your LAN or VPN and launch a session.
- Confirm video, sound and input work. If the session never starts, check allowed origins and the client’s direct route to the host.