From 6b9d02587b7e98e403ac1cf3cbae8c56f4f390b5 Mon Sep 17 00:00:00 2001 From: Sam Hadow Date: Thu, 23 Jul 2026 21:19:39 +0200 Subject: [PATCH] new blog post: self hosting element call --- .../2026-07-23-self_hosting_element_call.md | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 _posts/2026-07-23-self_hosting_element_call.md diff --git a/_posts/2026-07-23-self_hosting_element_call.md b/_posts/2026-07-23-self_hosting_element_call.md new file mode 100644 index 0000000..618723c --- /dev/null +++ b/_posts/2026-07-23-self_hosting_element_call.md @@ -0,0 +1,188 @@ +--- +layout: post +author: Sam Hadow +tags: messaging podman sysadmin +--- + +In this blog post I'll explain how to setup [element call](https://github.com/element-hq/element-call) using synapse as a matrix homeserver. This blog post assumes you already followed [this one](https://hadow.fr/blog/self-hosting-a-matrix-instance-with-podman.html). + +# steps + +## 1. enabling experimental features + +in the homeserver.yaml in your synapse homeserver add the following: + +```yaml +experimental_features: + # MSC3266: Room summary API. Used for knocking over federation + msc3266_enabled: true + # MSC4222 needed for syncv2 state_after. This allow clients to + # correctly track the state of the room. + msc4222_enabled: true + +# The maximum allowed duration by which sent events can be delayed, as +# per MSC4140. +max_event_delay_duration: 24h + +rc_message: + # This needs to match at least e2ee key sharing frequency plus a bit of headroom + # Note key sharing events are bursty + per_second: 0.5 + burst_count: 30 + +rc_delayed_event_mgmt: + # This needs to match at least the heart-beat frequency plus a bit of headroom + # Currently the heart-beat is every 5 seconds which translates into a rate of 0.2Hz + per_second: 1 + burst_count: 20 +``` + +## 2. generating keys for the livekit server + +run: + +```bash +podman run --rm livekit/livekit-server:latest generate-keys +``` + +And write down the API key and API secret. + + +## 3. running the containers for the livekit + +Don't forget to replace `YOUR_PUBLIC_IP`, `YOUR_API_KEY` and `YOUR_API_SECRET` + +```bash +mkdir -p /home/data/podman/element-call +``` + +in `/home/data/podman/element-call/config-livekit.yaml`: + +```yaml +port: 7880 + +bind_addresses: + - "0.0.0.0" + +rtc: + tcp_port: 7881 + port_range_start: 50100 + port_range_end: 50200 + + node_ip: YOUR_PUBLIC_IP + +room: + auto_create: false + +logging: + level: info + +turn: + enabled: false + +keys: + YOUR_API_KEY: YOUR_API_SECRET +``` + +For the containers replace `matrixrtc.hadow.fr`, `hadow.fr` and `synapse.hadow.fr` with your own domains. + +```bash +podman run -d \ + --pod=synapse \ + --name=element-call-jwt \ + --restart=unless-stopped \ + -e LIVEKIT_JWT_BIND=:8080 \ + -e LIVEKIT_URL=wss://matrixrtc.hadow.fr/livekit/sfu \ + -e LIVEKIT_KEY=YOUR_API_KEY \ + -e LIVEKIT_SECRET=YOUR_API_SECRET \ + -e LIVEKIT_FULL_ACCESS_HOMESERVERS=hadow.fr \ + -e MATRIX_HOMESERVER_URL=https://synapse.hadow.fr \ + ghcr.io/element-hq/lk-jwt-service:latest +``` + +```bash +podman run -d \ + --pod=synapse \ + --name=element-call-livekit \ + --restart=unless-stopped \ + -v /home/data/podman/element-call/config-livekit.yaml:/etc/livekit.yaml:ro,Z \ + docker.io/livekit/livekit-server:latest \ + --config /etc/livekit.yaml +``` + +You can now regenerate the files for the systemd services: + +```bash +cd ~/.config/systemd/user/ +podman generate systemd --restart-policy=on-failure --files --new --name synapse + +systemctl --user daemon-reload +systemctl --user restart pod-synapse.service +``` + + +Then complete in `pod-synapse.service` with the ports: + +```bash + -p 8008:8008 \ + -p 7880:7880 \ + -p 7881:7881 \ + -p 50100-50200:50100-50200/udp \ + -p 8070:8080 \ + -m=2048m +``` + +Don't forget to open the UDP range 50100-50200 and the TCP port 7881 in your firewall. For example with nftables: + +``` + #element call + tcp dport 7881 accept + udp dport 50100-50200 accept +``` + +## 4. nginx configuration files + +In a nginx configuration file (for example in `/etc/nginx/sites-available/matrixrtc.conf`): + +```nginx +server { + listen 443 ssl; + listen [::]:443 ssl; + server_name matrixrtc.hadow.fr; + + ssl_certificate /etc/letsencrypt/live/hadow.fr/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/hadow.fr/privkey.pem; + + location ^~ /livekit/jwt/ { + 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 $scheme; + proxy_pass http://127.0.0.1:8070/; + } + + location ^~ /livekit/sfu/ { + 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 $scheme; + proxy_send_timeout 120; + proxy_read_timeout 120; + proxy_buffering off; + proxy_set_header Accept-Encoding gzip; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_pass http://127.0.0.1:7880/; + } +} +``` + +In your well known in `/etc/nginx/sites-available/http.conf` modify the snippet with the following: + +```nginx + location /.well-known/matrix/client { + return 200 '{"m.homeserver": {"base_url": "https://synapse.hadow.fr"},"m.identity_server": {"base_url": "https://vector.im"},"org.matrix.msc4143.rtc_foci": [{"type": "livekit","livekit_service_url": "https://matrixrtc.hadow.fr/livekit/jwt"}]}'; + add_header Content-Type application/json; + add_header "Access-Control-Allow-Origin" *; + } +```