# MatrixRTC / LiveKit — Element X Calls **Last updated:** 2026-03-19 MatrixRTC enables voice/video calls in Element X using a LiveKit SFU backend. Both homeservers (`mx.vish.gg` and `matrix.thevish.io`) share the same LiveKit SFU on `matrix-ubuntu`. --- ## Service Overview | Property | Value | |----------|-------| | **Host** | matrix-ubuntu (`192.168.0.154`) | | **Matrix homeservers** | `mx.vish.gg` (synapse-mx.service) and `matrix.thevish.io` (synapse.service) — both on Synapse 1.148.0 | | **Compose file** | `hosts/vms/matrix-ubuntu/livekit.yml` (deployed manually at `/opt/livekit/`) | | **LiveKit version** | 1.9.12 | | **JWT service** | `ghcr.io/element-hq/lk-jwt-service:latest-ci` | --- ## Architecture ``` Element X ──→ mx.vish.gg (.well-known) ──→ livekit.mx.vish.gg/livekit/jwt (JWT service) ──→ matrix.thevish.io (.well-known) ──→ livekit.mx.vish.gg/livekit/sfu (LiveKit SFU) livekit.mx.vish.gg/ (LiveKit WS) ``` Both homeservers share the same LiveKit backend. - **NPM** on Calypso proxies `livekit.mx.vish.gg` → matrix-ubuntu - `/livekit/jwt/` → JWT service port 8089 (container 8080) - `/livekit/sfu/` → LiveKit SFU port 7880 - `/` → LiveKit SFU port 7880 (WebSocket for direct connections) - **DNS**: `livekit.mx.vish.gg` A record unproxied → `184.23.52.14` (home WAN) - **TLS**: Let's Encrypt cert issued via Cloudflare DNS challenge on matrix-ubuntu, copied to NPM as `npm-7` --- ## Endpoints | Endpoint | URL | Purpose | |----------|-----|---------| | JWT service healthz | `https://livekit.mx.vish.gg/livekit/jwt/healthz` | Health check | | JWT service SFU get | `https://livekit.mx.vish.gg/livekit/jwt/sfu/get` | Token exchange | | LiveKit SFU WS | `wss://livekit.mx.vish.gg/livekit/sfu/` | WebSocket signalling | | LiveKit HTTP | `https://livekit.mx.vish.gg/` | SFU API | | .well-known | `https://mx.vish.gg/.well-known/matrix/client` | RTC foci advertisement | --- ## Configuration Files on matrix-ubuntu | File | Purpose | |------|---------| | `/opt/livekit/docker-compose.yml` | LiveKit + JWT service deployment | | `/opt/livekit/livekit.yaml` | LiveKit SFU config (keys, RTC ports, external IP) | | `/opt/synapse-mx/homeserver.yaml` | Synapse config (MSCs, rate limits) | | `/etc/nginx/sites-available/mx-vish-gg` | nginx serving `.well-known` and Element static files | | `/etc/letsencrypt/live/livekit.mx.vish.gg/` | TLS cert (auto-renews, copies to NPM via deploy hook) | --- ## Synapse homeserver.yaml additions ```yaml # MatrixRTC / Element Call support experimental_features: msc3266_enabled: true # Room Summary API (knocking over federation) msc4222_enabled: true # state_after in sync v2 msc4140_enabled: true # Delayed events (call participation signalling) max_event_delay_duration: 24h rc_message: per_second: 0.5 burst_count: 30 rc_delayed_event_mgmt: per_second: 1 burst_count: 20 ``` --- ## .well-known/matrix/client Served by nginx at `https://mx.vish.gg/.well-known/matrix/client`: ```json { "m.homeserver": {"base_url": "https://mx.vish.gg"}, "org.matrix.msc4143.rtc_foci": [ { "type": "livekit", "livekit_service_url": "https://livekit.mx.vish.gg/livekit/jwt" } ] } ``` --- ## LiveKit SFU config (/opt/livekit/livekit.yaml) Key settings: - `use_external_ip: true` — auto-detects WAN IP `184.23.52.14` - `use_ice_lite: true` — optimised for server-side NAT traversal - `room.auto_create: false` — only lk-jwt-service creates rooms (security) - RTC ports: 7880 TCP (API/WS), 7881 TCP (RTC), 50000-60000 UDP (media) - **Note:** UDP 50000-60000 port range is NOT currently forwarded on the router — TURN relay is used instead via coturn at `turn:mx.vish.gg:3479` --- ## TLS Certificate Renewal Cert is issued on matrix-ubuntu via certbot + Cloudflare DNS plugin. A deploy hook copies it to NPM on Calypso after renewal: ``` /etc/letsencrypt/renewal-hooks/deploy/copy-to-npm.sh ``` If the hook fails, manually copy: ```bash ssh matrix-ubuntu sudo cp /etc/letsencrypt/live/livekit.mx.vish.gg/fullchain.pem \ /tmp/lk.crt sudo cp /etc/letsencrypt/live/livekit.mx.vish.gg/privkey.pem \ /tmp/lk.key scp -P 62000 /tmp/lk.crt Vish@100.103.48.78:/volume1/docker/nginx-proxy-manager/data/custom_ssl/npm-7/fullchain.pem scp -P 62000 /tmp/lk.key Vish@100.103.48.78:/volume1/docker/nginx-proxy-manager/data/custom_ssl/npm-7/privkey.pem ssh -p 62000 Vish@100.103.48.78 'sudo /usr/local/bin/docker exec nginx-proxy-manager nginx -s reload' ``` --- ## Troubleshooting ### Calls not working in Element X 1. Check `.well-known` is advertising foci: `curl https://mx.vish.gg/.well-known/matrix/client` 2. Check JWT service: `curl https://livekit.mx.vish.gg/livekit/jwt/healthz` 3. Check LiveKit is running: `ssh matrix-ubuntu "sudo docker ps | grep livekit"` 4. Check LiveKit logs: `ssh matrix-ubuntu "sudo docker logs livekit 2>&1 | tail -20"` ### Stuck calls See [How to resolve stuck MatrixRTC calls](https://sspaeth.de/2025/02/how-to-resolve-stuck-matrixrtc-calls/) — usually caused by delayed events not cleaning up. ### JWT service returns 400 Normal for unauthenticated requests. Means the service is running correctly. ### Restarting services ```bash ssh matrix-ubuntu cd /opt/livekit sudo docker compose restart ``` ### Restarting Synapse ```bash ssh matrix-ubuntu sudo systemctl restart synapse-mx.service ```