WebSocket Proxy Setup#
When using CloudXR.js with HTTPS hosting (for development or production), you need a WebSocket proxy with TLS support to establish secure connections from the browser to the CloudXR Runtime.
Overview#
A WebSocket proxy is required when:
Hosting your web application using HTTPS
Deploying to production environments with SSL certificates
Accessing CloudXR from remote networks or the internet
Using Pico 4 Ultra devices (which require HTTPS)
The proxy acts as a secure gateway, providing TLS termination for WebSocket connections.
Connection Architecture#
Without proxy (HTTP mode):
Browser →ws://<server>:49100→CloudXR Runtime
With proxy (HTTPS mode):
Browser →wss://<proxy>:48322 →CloudXR Runtime (ws://localhost:49100)
Important
When hosting your web application using HTTPS, you must configure a WebSocket proxy and connect using wss://. Browsers block non-secure WebSocket (ws://) connections from secure (HTTPS) pages due to mixed content security policies.
Available Proxy Examples#
CloudXR.js supports the following common deployment scenarios:
Deployment Scenario |
Example Solution |
Setup Complexity |
|---|---|---|
Local development with HTTP |
No proxy needed (direct |
None |
Development and testing with HTTPS |
Docker + HAProxy example |
Low |
Single-server production |
Docker + HAProxy example |
Low |
Kubernetes production |
nginx Ingress example |
Medium |
Example 1: Development Proxy (Docker + HAProxy)#
This example uses HAProxy in a Docker container for HTTPS development and single-server deployments.
Quick Start#
Build the proxy image:
cd proxy docker build -t cloudxr-wss-proxy .
Run the proxy:
docker run -d --name wss-proxy \ --network host \ -e BACKEND_HOST=localhost \ -e BACKEND_PORT=49100 \ -e PROXY_PORT=48322 \ cloudxr-wss-proxy
View logs:
docker logs -f wss-proxy
Configuration Options#
Variable |
Default |
Description |
|---|---|---|
|
|
CloudXR Runtime hostname or IP. |
|
|
CloudXR Runtime WebSocket signaling port. |
|
|
TLS proxy listening port. |
|
(empty) |
Comma-separated extra hostnames or IPs for the auto-generated certificate’s
|
|
|
Time between backend health checks. |
|
|
Consecutive successful checks to mark backend up. |
|
|
Consecutive failed checks to mark backend down. |
Note
The certificate is generated once on first run and cached on disk. To pick
up a change to CERT_HOSTNAMES, either delete the existing
server.pem (or wipe the volume if you persisted certificates) before
restarting the container.
Using Custom Certificates#
If you have your own TLS certificate, combine certificate and key into a PEM and mount it:
cat your-cert.crt your-key.key > server.pem
docker run -d --name wss-proxy \
--network host \
-v /path/to/server.pem:/usr/local/etc/haproxy/certs/server.pem:ro \
-e BACKEND_HOST=localhost \
-e BACKEND_PORT=49100 \
-e PROXY_PORT=48322 \
cloudxr-wss-proxy
Managing the Proxy#
docker stop wss-proxy
docker start wss-proxy
docker stop wss-proxy
docker rm wss-proxy
docker stop wss-proxy
docker rm wss-proxy
docker volume rm cloudxr-proxy-certs
Note
To persist self-signed certificates across container restarts, mount a Docker volume:
-v cloudxr-proxy-certs:/usr/local/etc/haproxy/certs.
Common Issues#
Connection refused at startup: Expected while CloudXR Runtime is still initializing.
Certificate warnings: Trust the proxy certificate on the headset or browser before connecting.
Firewall blocking: Ensure proxy TCP port (default
48322) is open.SSL handshake failure (``certificate unknown``) from a LAN client:
CERT_HOSTNAMESis missing the IP or hostname the client is using. Set it, remove the existingserver.pem, and restart.
Browser Cross-Origin Access (Private Network Access)#
Modern Chromium-based browsers restrict requests from a public origin (for
example a page served from https://example.github.io) to a private IP
(for example https://10.x.x.x:48322) under the Private Network Access
policy.
The proxy includes the server-side opt-in on every response:
Access-Control-Allow-Private-Network: true
This satisfies the preflight requirement. However, Chrome may additionally
require a per-site user permission for the calling page. If a phone or
desktop browser reports “Permission was denied for this request to access the
local address space,” grant local-network permission on the
calling origin (the page making the request), not on the proxy URL.
If you cannot or do not want to grant the permission, the simplest workaround is to host the WebXR application itself on the same private network as the proxy (private-to-private requests are not subject to the policy).
Example 2: Production Proxy (Kubernetes + nginx)#
For Kubernetes deployments, use nginx Ingress with TLS termination and WebSocket routing.
Typical setup flow:
Create TLS secret.
Deploy nginx proxy service and configuration.
Configure Ingress for HTTPS + WebSocket traffic.
Validate endpoint, then connect clients using
wss://.
For CloudXR.js path-based proxy routing behavior, refer to Session API.
Firewall Configuration#
Refer to Ports and Firewalls for required ports and firewall configuration instructions.