Whisper-Server (Flurfunk) — Setup & Operations
Der prilog-wisper Server ist der zentrale Voice-to-Text-Dienst für alle Prilog-Tenants. Er läuft als einzelne Hetzner-Instanz im Tailnet und erreicht alle Kunden-Server (und das Backend) über stabile Tailscale-MagicDNS-Hostnamen. Audio wird zu keinem Zeitpunkt persistiert — der Server ist stateless im Sinne der Inhalte, hat aber einen Modell-Cache auf der lokalen Disk damit Restarts schnell sind.
Diese Seite ist das vollständige Howto für Provisionierung, Inbetriebnahme, Monitoring und Recovery.
Architektur in 30 Sekunden
┌──────────────────────┐
│ prilog-wisper │
│ (Hetzner CCX33) │
│ │
┌────────────┐ │ Docker: │
│ Synapse │ │ faster-whisper- │
│ (Kunde 1) │──┐ │ server :8000 │
└────────────┘ │ │ │
│ │ Modell: │
┌────────────┐ │ │ large-v3 int8 │
│ Synapse │ │ Tailnet │ │
│ (Kunde 2) │──┼───────────────▶│ RAM: ~3 GB │
└────────────┘ │ │ Disk: ~3 GB Cache │
│ └──────────────────────┘
┌────────────┐ │ ▲
│ Backend-API│──┘ │
│ (api.*) │ │
└────────────┘ │
│ │
│ POST /v1/audio/transcriptions │
└─────────────────────────────────────┘Datenfluss pro Sprachnachricht:
- Mitarbeiter nimmt Audio im Web-Client auf, sendet als
m.audioan Synapse - Synapse persistiert das Event, der Prilog-Matrix-Connector-Hook (
on_new_event) erkenntmsgtype: m.audiound filtert - Connector-Hook ruft
POST https://api.prilog.chat/api/matrix-connector/transcribe-voicemittenantKey,roomId,eventId,mxcUri - Backend orchestriert: lädt Audio via Synapse-Admin-API, schickt sie an
http://prilog-wisper:8000/v1/audio/transcriptions(Tailnet-Hostname!) - Whisper transkribiert und antwortet mit
{"text": "..."}— Latenz ca. 7s + 0.4s pro Audio-Sekunde (large-v3 int8 auf CCX33) - Backend postet das Transkript als Reply-Message via Synapse-Admin-API in den Original-Raum, mit Custom-Feldern
org.prilog.transcript_forundtranscript_text - Web-Client empfängt das Reply-Event via Sync, hängt das Transkript inline unter die Audio-Bubble
Wichtig: Schritt 4 nutzt den MagicDNS-Hostnamen prilog-wisper, nicht eine IP. Tailscale verteilt den Namen über das Tailnet, die IP kann sich ändern (selten, aber möglich), der Name bleibt stabil.
Hardware-Empfehlung
| Tier | Hetzner | vCPU | RAM | Disk | Latenz 30s Audio | Modell |
|---|---|---|---|---|---|---|
| Empfohlen | CCX33 | 4 dedicated EPYC | 32 GB | 240 GB | ~20 s | large-v3 int8 |
| Budget-Notlösung | CX31 | 2 shared | 8 GB | 80 GB | ~60 s | distil-large-v3 |
| Wachstum | CCX43 | 8 dedicated EPYC | 64 GB | 360 GB | ~10 s | large-v3 int8 |
| GPU-Upgrade | GEX44 | 8 vCPU + RTX 4000 | 64 GB | 360 GB | ~3 s | large-v3 fp16 |
Bei < 100 aktiven Mitarbeitern reicht der CCX33 aus, bei 100–500 sollte man zum CCX43 wechseln, bei > 500 lohnt sich der GPU-Switch.
Burst vs. Average
Die Latenz-Werte oben sind single-request. Bei parallelen Aufnahmen wird der Server linear langsamer (single-thread pro Request). 5 parallele 30s-Aufnahmen auf einem CCX33 = ~100 s für die letzte. Das ist okay solange „peak hours" nicht voll mit Sprachnachrichten überfüllt sind.
Provisionierung Schritt für Schritt
Diese Anleitung beschreibt wie ein neuer Whisper-Server aufgesetzt wird. Brauchst du nur, wenn der bestehende ausfällt oder du ein Upgrade machen willst.
1. Hetzner-Server bestellen
- Hetzner Cloud Console → Add Server
- Location: nbg1 (Nürnberg) oder fsn1 (Falkenstein) — egal welche
- Image: Ubuntu 24.04
- Type: CCX33 (oder größer)
- Networking: nur IPv4 + IPv6 öffentlich, kein Firewall-Profil zuweisen (das machen wir mit ufw selbst)
- SSH Keys: lee-personal (oder welcher Key auch immer der Prilog-Operator nutzt)
- Name: prilog-wisper (oder bei Wachstum:
prilog-wisper-2etc.) - Create
Notiere dir die öffentliche IP — du brauchst sie nur einmal, für den ersten SSH-Login.
2. Erstkonfiguration: User anlegen, SSH absichern
ssh root@<public-ip>
adduser lee
usermod -aG sudo lee
echo 'lee ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/lee-nopasswd
chmod 440 /etc/sudoers.d/lee-nopasswd
# SSH-Key kopieren
mkdir -p /home/lee/.ssh && chmod 700 /home/lee/.ssh
cp /root/.ssh/authorized_keys /home/lee/.ssh/
chown -R lee:lee /home/lee/.ssh3. Tailscale verbinden
Tailscale ist Pflicht — ohne ist der Server nicht im Prilog-Netz und nicht erreichbar.
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --auth-key=<TAILSCALE_AUTH_KEY> --hostname=prilog-wisper
tailscale ip -4 # zeigt die zugeteilte 100.x.y.z AdresseAuth-Key
Der Tailscale Auth-Key liegt in den Prilog-Secrets. Verwende einen ephemeral=false reusable=true Key. Hostname unbedingt prilog-wisper setzen — der Backend ruft den Server unter genau diesem Namen via MagicDNS.
Verifizieren: von einem anderen Tailnet-Mitglied (z.B. api.prilog.chat):
ping prilog-wisper # MagicDNS-Lookup sollte funktionieren4. SSH-Konfig auf der Operator-Maschine
Auf deiner lokalen Maschine in ~/.ssh/config:
Host prilog-wisper
User lee
IdentityFile ~/.ssh/prilog
IdentitiesOnly yesAb jetzt: ssh prilog-wisper — kein IP-Tippen mehr nötig.
5. UFW-Firewall einrichten
Wir lassen nur Tailnet-Traffic auf Port 8000 zu. Port 22 bleibt für SSH offen (öffentlich, da wir keinen Tailscale-only SSH-Zugang konfiguriert haben).
ssh prilog-wisper
sudo apt-get install -y ufw
sudo ufw --force reset
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow in on tailscale0 to any port 22
sudo ufw allow in on tailscale0 to any port 8000
sudo ufw allow in 22/tcp
sudo ufw --force enable
sudo ufw status verboseErwartete Ausgabe:
22 on tailscale0 ALLOW IN Anywhere
8000 on tailscale0 ALLOW IN Anywhere
22/tcp ALLOW IN Anywhere6. Docker installieren
Wir nehmen die offizielle Docker-Repo, nicht das Ubuntu-Standard-Paket.
sudo apt-get update -qq
sudo apt-get install -y -qq ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
sudo apt-get update -qq
sudo apt-get install -y -qq docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker lee
docker --version # Sollte 27.x oder neuer sein
docker compose version # v2.30+ oder neuergroupchange
Nach usermod -aG docker lee musst du die SSH-Session neu öffnen, sonst hat dein Shell die neue Gruppe noch nicht.
7. faster-whisper-server in Docker starten
sudo mkdir -p /opt/whisper && sudo chown lee:lee /opt/whisper
mkdir -p /opt/whisper/cache
cat > /opt/whisper/docker-compose.yml <<'YAML'
services:
whisper:
image: fedirz/faster-whisper-server:latest-cpu
container_name: prilog-whisper
restart: unless-stopped
network_mode: host
environment:
WHISPER__MODEL: Systran/faster-whisper-large-v3
WHISPER__INFERENCE_DEVICE: cpu
WHISPER__COMPUTE_TYPE: int8
ENABLE_UI: 'false'
LOG_LEVEL: info
DEFAULT_LANGUAGE: de
volumes:
- /opt/whisper/cache:/root/.cache/huggingface
YAML
cd /opt/whisper
sudo docker compose up -dZur Erklärung der Settings:
network_mode: host— Container teilt sich den Netzwerk-Stack des Hosts. Lauscht direkt auf0.0.0.0:8000. Zusammen mit der UFW-Regel kommt nur Tailnet-Traffic durch.compute_type: int8— INT8-Quantisierung. Halbiert den RAM-Verbrauch und läuft auf EPYC-AVX2-Cores fast genauso schnell wie fp16. Qualitativ fehlerfrei für Deutsch.large-v3— das beste Modell für deutsche Sprache. Distil-Varianten sind ~2x schneller, aber ~3% Erkennungsfehler — für Schul-Kontext nicht okay.cache:/root/.cache/huggingface— der Modell-Download (ca. 3 GB) wird persistent gespeichert. Nach einem Container-Restart läuft er sofort wieder ohne Re-Download.
8. Modell-Download abwarten
Der erste Start lädt das Modell von HuggingFace. Das dauert je nach Verbindung 2–10 Minuten.
sudo docker logs -f prilog-whisperDu wirst sehen, wie das Modell heruntergeladen wird. Sobald folgendes erscheint, ist der Server bereit:
INFO: Started server process [27]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000Ctrl+C raus aus dem Log-Tail (der Container läuft weiter im Hintergrund).
9. Health-Check
Vom Whisper-Server selbst:
curl http://localhost:8000/health
# OKVon einem anderen Tailnet-Mitglied (z.B. der Operator-Maschine oder api.prilog.chat):
curl http://prilog-wisper:8000/health
# OKWenn der zweite Aufruf 0K liefert, ist die Provisionierung abgeschlossen.
10. Smoke-Test mit echtem Audio
Bevor du den Server in Produktion meldest, mach einen echten Transkriptions-Test mit einer kurzen deutschen Sprachprobe. Beispiel:
curl -X POST http://prilog-wisper:8000/v1/audio/transcriptions \
-F "file=@test-5s.m4a" \
-F "model=Systran/faster-whisper-large-v3" \
-F "language=de" \
-F "response_format=json"Erwartete Ausgabe (Beispiel):
{"text":"Dies ist eine schöne Geschichte von einem großen und kleinen Hasen."}Latenz für eine 5-Sekunden-Datei: ~7 Sekunden im warmen Zustand (erster Call braucht ~20s wegen Modell-Lazy-Load — danach läuft das Modell im RAM).
Backend einbinden
Sobald der Server steht, muss das Prilog-Backend ihn kennen.
Backend .env setzen
Auf lee@91.99.30.243:/var/www/backend-api/.env:
WHISPER_BASE_URL=http://prilog-wisper:8000
WHISPER_MODEL=Systran/faster-whisper-large-v3
WHISPER_LANGUAGE=deDiese Variablen sind optional — der Default in src/config/env.ts ist genau dieser Wert. Wenn du nichts ins .env schreibst, ist der Server trotzdem korrekt konfiguriert. Du brauchst die Variablen nur, wenn du eine zweite Whisper-Instanz hast (z.B. prilog-wisper-2) und auf die switchen willst.
Nach der Änderung: pm2 restart backend-api.
Verifizieren
Im Prilog-Admin-Dashboard sollte die „Flurfunk-Server"-Card jetzt grün und einen Health-Check-Wert von ca. 5–50 ms zeigen. Falls rot: siehe Troubleshooting unten.
Operations
Status prüfen
Schnell: das Prilog-Admin-Dashboard zeigt den Status an, gecached für 30 Sekunden.
Manuell:
ssh prilog-wisper
sudo docker ps --filter name=prilog-whisper
sudo docker logs --tail 50 prilog-whisper
sudo docker stats prilog-whisper --no-streamErwartete Werte für einen idle Server:
- Container:
Up X hoursohne Restarts - RAM: ~2.5 GB belegt (large-v3 int8 im RAM)
- CPU: < 1% idle
- Logs: keine Errors, nur access logs bei Requests
Restart
ssh prilog-wisper
cd /opt/whisper && sudo docker compose restart whisperRestart dauert ca. 5 Sekunden (Modell ist im persistenten Cache, kein Re-Download). Während des Restarts schlagen Transkriptionen fehl — das Backend loggt das nur als Warning, Sprachnachrichten selbst sind nicht betroffen.
Modell wechseln (z.B. auf distil)
Wenn du auf das schnellere distil-large-v3 Modell umstellen willst (z.B. weil der Server überlastet ist und die Qualität trotzdem reicht):
ssh prilog-wisper
sudo nano /opt/whisper/docker-compose.yml
# WHISPER__MODEL ändern auf: Systran/faster-distil-whisper-large-v3
sudo docker compose up -dBeim nächsten Start lädt sich das neue Modell aus HuggingFace (~1.5 GB, kleiner als das volle large-v3). Latenz halbiert sich, Erkennungsfehler steigen auf ca. 3% (für Deutsch immer noch gut).
Updaten von faster-whisper-server
ssh prilog-wisper
cd /opt/whisper
sudo docker compose pull whisper
sudo docker compose up -d whisperUpdate läuft transparent — alte Container wird gestoppt, neuer Container startet mit dem gleichen Modell-Cache.
Backups
Was muss gesichert werden: nichts. Der Whisper-Server ist komplett stateless:
- Audio-Bytes werden nicht gespeichert
- Transkripte werden im Backend (api.prilog.chat) verarbeitet und in Synapse geschrieben — Backups dort liegen
- Modell ist im Cache, kann jederzeit re-downloaded werden
docker-compose.yml(eine YAML-Datei) ist im git tracked falls duprilog-infrapflegst, sonst trivial neu zu schreiben
Wenn der Server abbrennt, bestellst du einfach einen neuen, machst die Schritte oben durch (15-20 Minuten + Modell-Download), und das System läuft weiter.
Logs einsehen
ssh prilog-wisper
sudo docker logs prilog-whisper --tail 100 --since 1hInteressante Patterns:
Whisper request completed in X ms— normale Transkriptions-Requests400 Bad Request— Backend hat eine ungültige Audio-Datei geschickt (z.B. korrupt, falscher Content-Type)Model loading...— wird nur beim Container-Start geloggt- Memory-Errors wären
OutOfMemoryError— sollte nie passieren bei int8 + 32 GB
Troubleshooting
Status im Admin-Dashboard ist rot
1. Vom Operator-Rechner per Tailnet probieren:
curl -v http://prilog-wisper:8000/healthErwartet: OK. Wenn DNS-Auflösung scheitert, ist der Server nicht im Tailnet — prüfe tailscale status auf prilog-wisper. Falls Connection refused: Container ist down → siehe nächster Schritt.
2. Container-Status prüfen:
ssh prilog-wisper
sudo docker ps -a --filter name=prilog-whisperWenn Exited: sudo docker logs prilog-whisper --tail 100 zeigt warum. Restart: cd /opt/whisper && sudo docker compose up -d.
3. Disk voll?
ssh prilog-wisper
df -h /Wenn / zu mehr als 95% voll: Modell-Cache prüfen, docker system prune falls viele alte Images/Container hängen.
4. Backend kann ihn nicht erreichen:
ssh lee@91.99.30.243
curl http://prilog-wisper:8000/healthWenn das fehlschlägt, ist Tailnet-Routing kaputt — sehr selten, neu starten mit sudo systemctl restart tailscaled auf beiden Seiten.
Transkriptions-Latenz sehr hoch (>60s für 30s Audio)
Mehrere Ursachen möglich:
- Burst-Last: mehrere Requests gleichzeitig. Whisper läuft single-thread pro Request, also ist der zweite Request blockiert bis der erste durch ist. Lösung: warten bis Last sich verteilt, oder Server upgraden auf CCX43.
- Fremd-Last auf der CPU: prüfe
sudo docker stats prilog-whisper. Wenn CPU > 100% vergeben aber Latenz hoch ist → System-Load prüfen viatop. Auf einem dediziertem Whisper-Server sollte sonst nichts laufen. - Modell-Lazy-Load: der allererste Request nach einem Restart braucht ~20s länger weil das Modell aus dem Cache in den RAM geladen wird. Ist einmalig, danach läuft es im warmen Zustand.
Whisper antwortet mit halluziniertem Text bei Stille
Whisper hat eine bekannte Macke: bei sehr leiser oder rauschiger Aufnahme erfindet es manchmal Trainingsdaten („Untertitel von Stephanie Geiges", „Vielen Dank fürs Zuschauen"). Gegenmittel sind im Default-Setup eingebaut:
- VAD (Voice Activity Detection) filtert Stille raus
- Temperature 0 macht das Modell deterministisch
language=deHint gibt klare Sprache vor
Wenn trotzdem Halluzinationen kommen: ist die Audio einfach zu schlecht (zu leise, zu viel Hintergrundlärm) — User soll nochmal näher am Mikro aufnehmen.
Server ist erreichbar aber jeder Request 500'd
Container-Logs prüfen:
ssh prilog-wisper
sudo docker logs prilog-whisper --tail 100Häufigste Ursachen:
- HuggingFace-Modell konnte nicht geladen werden (HF down oder Cache korrupt) —
rm -rf /opt/whisper/cache && sudo docker compose up -d, dann lädt es neu - Out-of-Memory — sollte bei 32 GB RAM und int8 nie passieren, aber wenn: Server hat zu wenig RAM, Upgrade auf größere Instanz nötig
Wie weiß ich ob Flurfunk gerade läuft
Drei Check-Punkte:
- Whisper-Server alive: Admin-Dashboard → Flurfunk-Server-Card grün
- Backend kann ihn ansprechen:
ssh lee@91.99.30.243 'curl -sS http://prilog-wisper:8000/health' - End-to-End: nimm im Web-Client eine Test-Sprachnachricht auf, prüfe ob das Transkript innerhalb von 30 Sekunden ankommt
Wenn alle drei grün sind, läuft Flurfunk fehlerfrei.
Migrations-Pfad zu GPU
Wenn du irgendwann auf einen GPU-Server (Hetzner GEX44) wechseln willst:
Neuen GEX44 mit gleichem Hostname
prilog-wisper-gpuprovisionierenDie Schritte 1–6 dieser Anleitung wiederholen (mit zusätzlich
nvidia-container-toolkitfür Docker-GPU-Support)docker-compose.ymlanpassen:yamlimage: fedirz/faster-whisper-server:latest-cuda environment: WHISPER__INFERENCE_DEVICE: cuda WHISPER__COMPUTE_TYPE: float16 deploy: resources: reservations: devices: - capabilities: [gpu]Modell ist gleich —
WHISPER__MODELbleibtSystran/faster-whisper-large-v3, nur compute_type ändert sichBackend
.envumstellen:WHISPER_BASE_URL=http://prilog-wisper-gpu:8000pm2 restart backend-apiauf api.prilog.chatSmoke-Test, dann alten CCX33 abschalten
Latenz auf GPU: ca. 3 Sekunden für 30 Sekunden Audio — Faktor 7x schneller. Damit wird Flurfunk fast-synchron, das Transkript erscheint quasi unmittelbar.
Verwandte Themen
- Infrastruktur-Übersicht — alle Prilog-Server im Überblick
- Deployment & Betrieb — wie das Backend deployed wird
- Flurfunk Anwender-Handbuch — Endnutzer-Sicht
- Flurfunk Admin-Konfiguration — Schul-Admin-Sicht