Files
homelab/services/coturn/README.md
T
x3 4a55944aa8 Homelab-Dokumentation und Konfigurationsvorlagen
Dokumentation aus dem Wiki uebernommen und um die Dienste auf dem Server
erweitert. Alle Anleitungen sind so geschrieben, dass sich der jeweilige
Dienst allein daraus neu aufsetzen laesst.

docs/      Dienste im Heimnetz (aus dem Wiki, anonymisiert)
services/  Dienste auf dem Server mit Konfigurationsvorlagen
           - matrix-synapse: Homeserver, Postgres, Well-Known-Delegation
           - coturn:         TURN-Relay fuer Anrufe
           - element-web:    Web-Client
           - wikijs, gitea, nginx

Durchgehend anonymisiert: echte Domain durch example.com ersetzt, IP-Adressen
und E-Mail-Adressen durch Platzhalter. Konfigurationsdateien liegen nur als
.example mit Platzhaltern statt echter Secrets vor.

Die Anleitungen halten die Stolpersteine fest, die beim Aufbau tatsaechlich
aufgetreten sind, unter anderem:
- Synapse verlangt LC_COLLATE=C, sonst startet es nicht
- server_name ist nachtraeglich nicht aenderbar -> Delegation noetig
- register_new_matrix_user liest conf.d nicht, Secret muss per -k kommen
- coturn braucht external-ip, sonst kommen keine Medien durch
- Gruppenmitgliedschaft fuer den Zertifikatszugriff wirkt erst beim Neustart
- nginx vererbt add_header nicht in location-Bloecke mit eigenen Direktiven
2026-08-31 17:56:02 +00:00

7.3 KiB
Raw Blame History

TURN-Server (coturn)

TURN-Relay für Sprach- und Videoanrufe in Matrix/Element.

Punkt Wert
Software coturn
Öffentlich turn.example.com
Ports 3478 TCP/UDP (STUN/TURN), 5349 TCP/UDP (TURNS), 4915265535 UDP (Relay)
Authentifizierung Zeitbegrenzt über gemeinsames Secret

Wozu überhaupt?

WebRTC verbindet zwei Teilnehmer möglichst direkt. Sitzen beide hinter NAT oder einer restriktiven Firewall, klappt das nicht der Anruf klingelt, aber es kommt kein Ton an. Der TURN-Server leitet den Medienstrom dann weiter.

Er ist damit ein Relay für fremden Datenverkehr. Ohne Absicherung ist das ein offener Proxy, mit dem sich das interne Netz des Servers erreichen lässt. Der Abschnitt „Absicherung“ unten ist deshalb nicht optional.

Kein festes Passwort

coturn und Synapse teilen sich ein Secret (static-auth-secret bzw. turn_shared_secret). Fragt ein Client Zugangsdaten an, erzeugt Synapse daraus per HMAC ein kurzlebiges Paar:

  • Benutzername: <ablaufzeitpunkt>:<benutzer-id>
  • Passwort: HMAC-SHA1 über den Benutzernamen mit dem Secret, Base64-kodiert

coturn rechnet dasselbe nach. Es gibt also keine angelegten Benutzer beide Seiten müssen nur exakt dasselbe Secret kennen.

1. Installation

sudo apt-get install -y coturn

# Das Debian-Paket startet ohne diesen Schalter nicht.
sudo sed -i 's/^#\?TURNSERVER_ENABLED=.*/TURNSERVER_ENABLED=1/' /etc/default/coturn

2. Secret erzeugen

openssl rand -hex 32

Derselbe Wert muss in /etc/turnserver.conf (static-auth-secret) und in der Synapse-Konfiguration (turn_shared_secret) stehen.

3. Konfiguration

turnserver.conf.example nach /etc/turnserver.conf kopieren und anpassen:

sudo cp turnserver.conf.example /etc/turnserver.conf
sudo chown root:turnserver /etc/turnserver.conf
sudo chmod 640 /etc/turnserver.conf

Zwingend zu ersetzen:

  • static-auth-secret der oben erzeugte Wert
  • external-ip die öffentliche IP des Servers
  • realm / server-name die eigene Domain

external-ip nicht vergessen

Ohne external-ip kündigt coturn hinter NAT seine private Adresse als Relay-Kandidat an. Der Anruf baut sich dann scheinbar auf, aber es fließen keine Medien. Zu ermitteln mit:

curl -s -4 ifconfig.me

4. Zertifikat lesbar machen

turns: auf Port 5349 braucht Zertifikat und Key. coturn läuft als Benutzer turnserver und kommt an Let's-Encrypt-Material standardmäßig nicht heran.

Statt die Rechte global aufzuweichen, eine eigene Gruppe verwenden:

sudo groupadd -f ssl-cert
sudo usermod -aG ssl-cert turnserver

sudo chgrp -R ssl-cert /etc/letsencrypt/live /etc/letsencrypt/archive
sudo chmod -R g+rX     /etc/letsencrypt/live /etc/letsencrypt/archive
sudo find /etc/letsencrypt/archive -name 'privkey*.pem' -exec chmod 640 {} \;

Gruppenmitgliedschaften greifen erst beim Start eines Prozesses. Läuft coturn schon, ist danach ein systemctl restart coturn nötig ein reload reicht nicht.

Prüfen, ob es wirklich klappt:

sudo -u turnserver head -c 30 /etc/letsencrypt/live/matrix.example.com/privkey.pem

Erneuerung berücksichtigen

coturn liest das Zertifikat nur beim Start. Nach einer Erneuerung würde es mit dem alten weiterarbeiten, bis es irgendwann abläuft. Deshalb ein Deploy-Hook nach /etc/letsencrypt/renewal-hooks/deploy/10-reload-services.sh:

#!/bin/sh
set -e
chgrp -R ssl-cert /etc/letsencrypt/live /etc/letsencrypt/archive || true
chmod -R g+rX     /etc/letsencrypt/live /etc/letsencrypt/archive || true
find /etc/letsencrypt/archive -name 'privkey*.pem' -exec chmod 640 {} \; || true
systemctl reload  nginx  || true
systemctl restart coturn || true

Ausführbar machen: sudo chmod +x …/10-reload-services.sh

5. Absicherung

Die denied-peer-ip-Zeilen in der Beispielkonfiguration sperren alle privaten und reservierten Netze. Ohne sie kann jeder, der Zugangsdaten bekommt, über den TURN-Server auf localhost und das interne Netz zugreifen inklusive Diensten, die nur lokal lauschen.

Weitere gesetzte Schalter:

Schalter Wirkung
no-tcp-relay Kein TCP-Relay (RFC 6062). WebRTC braucht es nicht.
no-multicast-peers Kein Relay an Multicast-Adressen
no-cli Telnet-Verwaltungsschnittstelle aus
no-tlsv1, no-tlsv1_1, no-sslv3 Veraltete Protokolle aus
user-quota, total-quota Begrenzt den Missbrauch

6. Start und Prüfung

sudo systemctl enable --now coturn
sudo systemctl status coturn

Lauschen sollte er auf beiden Ports:

ss -tuln | grep -E '3478|5349'

Im Log darf nicht stehen „cannot start TLS and DTLS listeners“. Das heißt, dass coturn das Zertifikat nicht lesen kann:

sudo journalctl -u coturn | grep -iE 'certificate|private key|TLS'

Relay tatsächlich testen

Ein Blick auf offene Ports genügt nicht erst eine echte Allocation beweist, dass Authentifizierung und Relay funktionieren. Zugangsdaten selbst berechnen:

python3 - 'HIER-DAS-TURN-SECRET' <<'PY'
import hashlib, hmac, base64, time, sys
secret = sys.argv[1]
user = f"{int(time.time()) + 3600}:testuser"
pw = base64.b64encode(
    hmac.new(secret.encode(), user.encode(), hashlib.sha1).digest()).decode()
print(user); print(pw)
PY

Damit eine Allocation anfordern:

turnutils_uclient -u '<benutzername>' -w '<passwort>' -y -n 2 turn.example.com -p 3478

Erfolg sieht so aus gleich viele gesendete und empfangene Nachrichten, keine verlorenen Pakete:

start_mclient: tot_send_msgs=8, tot_recv_msgs=8
Total lost packets 0 (0.000000%)

Gegenprobe mit falschem Passwort muss Cannot complete Allocation liefern.

turnutils_uclient -T … schlägt mit error 442 fehl. Das ist kein Fehler: -T fordert ein TCP-Relay an, das per no-tcp-relay gesperrt ist.

TLS-Listener separat prüfen:

openssl s_client -connect turn.example.com:5349 -servername turn.example.com </dev/null

Der Domainname muss im Zertifikat stehen:

openssl s_client -connect turn.example.com:5349 </dev/null 2>/dev/null \
  | openssl x509 -noout -ext subjectAltName

7. Verbindung zu Synapse

In /etc/matrix-synapse/conf.d/homelab.yaml stehen turn_uris und turn_shared_secret. Nach dem Neustart lässt sich prüfen, was ein Client wirklich ausgeliefert bekommt:

curl -s -H "Authorization: Bearer <access-token>" \
  https://matrix.example.com/_matrix/client/v3/voip/turnServer

Die zurückgegebenen Zugangsdaten sollten mit turnutils_uclient funktionieren das ist der eigentliche Ende-zu-Ende-Nachweis.

Firewall

Freizugeben sind:

  • 3478 TCP und UDP
  • 5349 TCP und UDP
  • 4915265535 UDP (Relay-Bereich)

Der Relay-Bereich wird oft vergessen. Ohne ihn kommt die Allocation zustande, aber es fließen keine Medien.

Stolpersteine

Symptom Ursache
Port 5349 fehlt, Log meldet „certificate file is not set properly“ turnserver darf den Key nicht lesen, oder Dienst lief schon vor der Gruppenänderung
Anruf klingelt, kein Ton/Bild external-ip fehlt oder Relay-Ports gesperrt
error 442 im Test -T verwendet; TCP-Relay ist absichtlich aus
Authentifizierung schlägt fehl Secret in coturn und Synapse verschieden
Nach Zertifikatserneuerung bricht TURNS ab Deploy-Hook fehlt