Version: 25.05.20
In dem folgenden Tutorial wollen wir als Ergänzung zu unserem Matrix-Synapse-Server (Docker) Element Call hinzufügen – ebenfalls als Docker-Container.
Seit Anfang 2025 funktionieren Anrufe über ElementX nicht mehr automatisch über Jitsi oder Coturn wie bei Element Classic, sondern das ElementCall-Backend (LiveKit und JWT) für ElementX muss selbst gehostet werden.
Da ElementClassic nicht mehr weiterentwickelt wird, müssen alle User demnächst auf ElementX wechseln. Für Element Call (über LiveKit und JWT) sprechen dabei aber viele gute Eigenschaften:
✅ Dezentralisiert & Föderiert – Keine zentrale Instanz; funktioniert über verschiedene Matrix-Homeserver hinweg.
✅ Ende-zu-Ende-Verschlüsselt – Sichere und private Gespräche.
✅ Eigenständig & im Widget-Modus – Als eigenständige App nutzbar oder in Matrix-Clients einbettbar.
✅ WebRTC-basiert – Keine zusätzliche Software erforderlich.
✅ Skalierbar mit LiveKit – Unterstützt große Meetings über SFU (MSC4195: MatrixRTC mit LiveKit-Backend).
✅ Hand heben – Teilnehmer können signalisieren, wenn sie sprechen möchten, um den Gesprächsfluss zu organisieren.
✅ Emoji-Reaktionen – Nutzer können mit Emojis 👍️ 🎉 👏 🤘 reagieren und so für mehr Engagement und Interaktivität sorgen.
In unserem Tutorial werden wir LiveKit und den JWT-Service installieren, damit ElementCall funktioniert:

LiveKit und der JWT Service spielen zentrale Rollen in der Erweiterung von Element Call für große und skalierbare Meetings:
🔊 LiveKit – für skalierbare Sprach-/Videoanrufe
LiveKit ist ein SFU (Selective Forwarding Unit)-basierter Medienserver, der die Verarbeitung und Verteilung von Audio- und Videostreams übernimmt. Er wird benötigt, wenn viele Teilnehmer gleichzeitig an einem Meeting teilnehmen sollen – also über das hinaus, was Peer-to-Peer-Verbindungen (wie bei kleinen Calls) leisten können.
Warum notwendig?
- Ermöglicht große Meetings mit hoher Qualität.
- Entlastet Clients, da Streams zentral weiterverteilt werden.
🔐 JWT Service – für sichere Authentifizierung
Der JWT (JSON Web Token) Service stellt signierte Tokens aus, die Benutzer gegenüber LiveKit authentifizieren. Nur Clients mit gültigem Token dürfen einem Call über LiveKit beitreten.
Warum ist dieser notwendig?
- Verhindert unautorisierten Zugriff auf LiveKit.
- Ermöglicht eine sichere und kontrollierte Teilnahme an Anrufen.
Kurz gesagt:
🔧 LiveKit macht große, stabile Meetings möglich.
🔒 JWT Service sorgt dafür, dass nur berechtigte Nutzer daran teilnehmen dürfen.
Beide Komponenten sind unerlässlich für den skalierbaren und sicheren Betrieb von Element Call in einer produktiven Umgebung.
🛠 Voraussetzungen für diese Anleitung:
– Matrix Synapse ist nach diesem Tutorial bereits vollständig eingerichtet: Tutorial Deutsch
– sehr gute Linux und Terminal Kenntnisse
Bei dem Tutorial zu Matrix Synapse wurden schon einige Vorbereitungen für Element Call getroffen.
Anbei noch einmal die wichtigsten Vorbereitungen zusammengefasst:
⚠️ In der Firewall bei dem VPS-Anbieter oder in der UFW bei Linux, falls diese genutzt wird, wurden folgende Ports freigegeben:
TCP: 7881-7882 und UDP 50100 – 50200

⚠️ Das Docker-Netzwerk „matrixnetwork“ wurde eingerichtet, damit die Docker Container (Synapse, LiveKit und JWT) in einem Netzwerk sind:
docker network create --subnet=172.37.51.0/24 matrixnetwork
In der „homeserver.yaml“ Datei von Synapse müssen ebenfalls schon die Vorbereitungen für ElementCall eingetragen sein:
experimental_features:
msc3266_enabled: true
msc4222_enabled: true
msc4140_enabled: true
max_event_delay_duration: 24h
rc_message:
per_second: 0.5
burst_count: 30
rc_delayed_event_mgmt:
per_second: 1
burst_count: 20
# Aktiviert die Bereitstellung der .well-known URL zur Weiterleitung föderativer Kommunikation an Port 443 - wird später für Element Call benötigt !!
serve_server_wellknown: true
Und die Docker-Container Datei von Synapse muss dem Docker-Netzwerk hinzugefügt worden sein:
networks:
matrixnetwork:
ipv4_address: 172.37.51.13
Wenn das Tutorial befolgt worden ist, dann ist bereits alles richtig vorbereitet worden.
🔧 Installation von ElementCall (LiveKit und JWT)
Wir starten nun mit der Einrichtung von ElementCall und loggen uns auf dem Server ein:
ssh root@SERVER-IP
Zuerst sorgen wir dafür das der Server auf dem neusten Stand ist mit folgendem Befehl:
apt-get update && apt-get upgrade && apt-get autoremove
Nachdem alles auf den neusten Stand gebracht ist, erstellen wir ein Verzeichnis in unserem Docker-Projektordner (beispielsweise im /home/-Ordner):
cd /home/
mkdir elementcall
Wir wechseln in das Projektverzeichnis mit:
cd elementcall
Damit Livekit und der JWT Service verschlüsselt miteinander kommunizieren können, legen wir zwei zufällige Schlüsselpaare an, die wir uns notieren um diese später eintragen zu können. Dazu benutzen wir folgenden Befehl:
tr -dc 'a-zA-Z0-9' </dev/urandom | head -c 64
Nun richten wir die docker-compose.yml Datei im Ordner /elementcall/ ein:
nano docker-compose.yml
Die Docker-Compose enthält folgenden Inhalt, wobei einige Anpassungen vorzunehmen sind:
services:
matrix-element-call-jwt:
image: ghcr.io/element-hq/lk-jwt-service:latest
networks:
matrixnetwork:
ipv4_address: 172.37.51.14
container_name: matrix-element-call-jwt
hostname: matrix-element-call-jwt
environment:
- LK_JWT_PORT=8080
- LIVEKIT_URL=https://rtc.matrix.domain.com/livekit/sfu
- LIVEKIT_KEY=zsmqzHmunjc3CgfoiWzb8fGXpNZVJ2nQmzttzNkcA7qNWTGDJGCPPQiYp8sAudOB
- LIVEKIT_SECRET=uck5Phkw96pckJIzvsOBkrHfwgZv4d7NDT3uhjmzkl2YQvGUh4O50gYu54i83I2j
- LIVEKIT_LOCAL_HOMESERVERS=domain.com
restart: always
ports:
- :8080
matrix-element-call-livekit:
image: livekit/livekit-server:latest
networks:
matrixnetwork:
ipv4_address: 172.37.51.15
container_name: matrix-element-call-livekit
hostname: matrix-element-call-livekit
command: --dev --config /etc/livekit.yaml
ports:
- :7880/tcp
- 7881:7881/tcp
- 7882:7882/tcp
- 50100-50200:50100-50200/udp
restart: always
volumes:
- ./data/matrix-element-call-livekit/config.yaml:/etc/livekit.yaml:ro
networks:
matrixnetwork:
external: true
Folgende Anpassungen sind in der Datei vorzunehmen:
- matrixnetwork ipv4 Adressen: sind dem „Matrixnetwork“ (siehe oben, Docker Netzwerk) anzupassen, die IPs der einzelnen Container werden hier selbständig eingegeben (fortlaufende Reihenfolge für Synapse, LiveKit und JWT – .13 .14 .15 .16 usw)
- LIVEKIT_URL: hier kommt die Subdomain rein, unter der der LivekitServer erreichbar sein wird. Diese Subdomain muss später noch angelegt werden (siehe unten)
- LIVEKIT_KEY und LIVEKIT_SECRET: Hier kommen die zwei oben zufällig generierten Codes rein
- LIVEKIT_LOCAL_HOMESERVERS: hier kommt die Hauptdomain eures VPS Servers rein also keine Subdomain sondern die „domain.com“
Ein kurzer Hinweis zu der Umgebungsvariable:
LIVEKIT_LOCAL_HOMESERVERS=domain.com
Diese Variable gehört zu einer noch in Entwicklung befindlichen LiveKit-Funktion. Sie legt fest, welche Homeserver neue Anrufe starten dürfen. Künftig können damit nur Nutzer deines eigenen Homeservers Calls initiieren; föderierte Nutzer dürfen weiterhin beitreten, aber keine neuen Gespräche beginnen.
Praktisch ist das vor allem bei öffentlichen Diensten mit Zugangskontrolle. Noch hat die Einstellung keine Wirkung, kann aber bereits gesetzt werden, um vorbereitet zu sein.
Möchtest du mehreren Homeservern erlauben, neue Anrufe zu starten, gib sie einfach durch Komma getrennt an:
LIVEKIT_LOCAL_HOMESERVERS=domain.com,example.com
Als nächstes legen wir die LiveKit-Konfiguration an:
mkdir -p ./data/matrix-element-call-livekit && nano ./data/matrix-element-call-livekit/config.yaml
In die Datei kommt der folgende Inhalt rein (nano benutzen als Editor):
port: 7880
bind_addresses:
- "0.0.0.0"
rtc:
tcp_port: 7881
port_range_start: 50100
port_range_end: 50200
use_external_ip: false
ips:
includes:
- 111.150.60.108/32
logging:
level: debug
turn:
enabled: false
domain: localhost
cert_file: ""
key_file: ""
tls_port: 5349
udp_port: 443
external_tls: true
keys:
zsmqzHmunjc3CgfoiWzb8fGXpNZVJ2nQmzttzNkcA7qNWTGDJGCPPQiYp8sAudOB: "uck5Phkw96pckJIzvsOBkrHfwgZv4d7NDT3uhjmzkl2YQvGUh4O50gYu54i83I2j"
Zu den Variablen:
- includes: ist die IP-Adresse eures VPS (/32 mit angeben!)
- keys: sind die Keys oben aus der docker-compose.yml (zuerst der LIVEKIT_KEY und dann LIVEKIT_SECRET)
Als nächstes wird der DNS-Eintrag für ElementCall bei dem VPS anbieter gesetzt:
Ein A-Record ist dabei vollkommen ausreichend – AAAA wird nicht benötigt:
rtc.matrix.domain.com 7200 IN A 111.150.60.108
Nächster Schritt: Mit dem NGINX Proxy Manager (NPM) die Weiterleitungen einrichten

Wir legen die Domain an, die wir bereits in die docker-compose.yml eingetragen haben und für die auch bereits ein A-Record bei unserem VPS-Anbieter besteht (LIVEKIT_URL):

Die IP-Adresse die wir hier eintragen, ist dabei die interne Docker-IP (matrixnetwork) für den JWT Service aus der docker-compose.yml.
Wir müssen aber unter Advanced noch eine „Custom NGINX Proxy Configuration“ eingeben:

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://172.37.51.14:8080/;
}
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://172.37.51.15:7880/;
}
Auch hier wieder zur Erklärung: Die obere IP Adresse mit der 14 am Ende verweist auf den internen Docker Container JWT und die IP Adresse mit der 15 am Ende verweist auf die LiveKit IP Adresse, ebenfalls aus der docker-compose.yml.
Wir speichern den Eintrag im NPM Manager.
👉 Well-Known-Konfiguration
Ein essenzieller Bestandteil der Matrix-Konfiguration ist der .well-known
-Eintrag. Er teilt Clients wie ElementX mit, wo sich dein Homeserver befindet und wie das LiveKit-Backend für MatrixRTC (Element Call) erreichbar ist.
🔧 Entscheidend ist, auf welcher Domain der Eintrag verfügbar ist – diese muss mit dem server_name
aus der homeserver.yaml
übereinstimmen.
Zusätzlich relevant ist /.well-known/matrix/server
, der für die Föderation benötigt wird. Dieser verweist andere Server auf deinen Synapse-Endpunkt – vor allem wichtig bei Delegation oder wenn Synapse nicht direkt unter dem server_name
läuft.
📌 In der Regel wird .well-known/matrix/client
bei der Synapse-Installation automatisch erstellt. Für Element Call erweitern wir diesen nur. Dennoch gehen wir hier die gesamte Konfiguration durch, um bestehende Einträge korrekt anpassen zu können.
🔍 Domain-Zuordnung für .well-known
:
server_name: matrix.domain.com
→ .well-known
gehört auf https://matrix.domain.com.server_name: domain.com
→ .well-known
muss unter https://domain.com erreichbar sein.
Wir richten den .well-known Eintrag wieder unter „Advanced“ im NPM Manager ein:

proxy_buffering off;
proxy_redirect off;
client_max_body_size 0;
location /.well-known/matrix/client {
default_type application/json;
add_header Content-Type application/json;
add_header "Access-Control-Allow-Origin" *;
return 200 '{"m.homeserver": {"base_url": "https://matrix.domain.com"}, "m.identity_server": {"base_url": "https://vector.im"}, "org.matrix.msc4143.rtc_foci": [{"type": "livekit", "livekit_service_url": "https://rtc.matrix.domain.com/livekit/jwt"}, {"type": "nextgen_new_foci_type", "props_for_nextgen_foci": "val"}]}';
}
Wir haben alle Arbeiten erfolgreich abgeschlossen.
Nun schauen wir, dass wir wieder in das Projektverzeichnis gehen (/home/elementcall) und den Container starten:
cd /home/elementcall
docker compose up -d
Das Ergebnis können wir mit „docker ps“ in der Kommandozeile abfragen. Wir sollten sehen, dass die Container laufen:

✅ Was haben wir in diesem Tutorial erreicht?
Installation von ElementCall: LiveKit und JWT
- Matrix Synapse über die Zusatzfunktionen mit ElementCall verbunden
- Subdomains eingerichtet (rtc.matrix.domain.com)
- NGINX Proxy Manager richtig konfiguriert
- .well-known Eintrag bei der Domain richtig gesetzt
Viel Spaß beim sicheren telefonieren (mit Video) über ElementCall (LiveKit+JWT) und ElementX !
Das offizielle Projekt und weitere Anleitungen findet man unter Github: https://github.com/element-hq/element-call

bc1q8dxp9mlt3mkvaklu2vn8j6jteqyv53kschc90v

Lightning: itsc@strike.me
