MiroTalk SFU selbst hosten: Video-Konferenzen auf eigenen Servern
Zoom, Teams und Google Meet sind bequem – aber deine Gespräche laufen über fremde Server. MiroTalk SFU bringt dir eigene, browserbasierte Video-Konferenzen: WebRTC mit Mediasoup als SFU (Selective Forwarding Unit), ohne Installation für die Teilnehmer:innen, mit Chat, Whiteboard, Dateiteilung und mehr – komplett auf deiner Hardware.
Warum MiroTalk SFU?
MiroTalk SFU ist eine quelloffene Video-Konferenz-Lösung, die vollständig im Browser läuft – keine App-Installation bei den Gästen, keine fremden Server dazwischen. Der Name verrät die Technik: Ein SFU (Selective Forwarding Unit) verteilt die Audio- und Video-Streams zentral an alle Teilnehmer:innen – effizienter als reine Peer-to-Peer-Lösungen, sobald mehr als zwei Personen im Raum sind.
Genau wie bei Jitsi Meet kannst du Räume per Link teilen – im Gegensatz zu kommerziellen Diensten bleiben Verbindung, Aufzeichnung (optional) und alle Daten unter deiner Kontrolle.
Konzept: SFU & die drei Port-Bereiche
Bevor es ans Setup geht, lohnt ein Blick auf die Netzwerk-Architektur – hier scheitern die meisten Installationen:
- Port 3010 (TCP): Web-UI und Signaling (WebSocket). Die Teilnehmer:innen laden hier die Seite und tauschen Session-Informationen aus. Wir binden ihn nur an
127.0.0.1und legen einen Reverse Proxy davor. - Ports 40000–40100 (TCP + UDP): Die eigentlichen WebRTC-Medien (Audio/Video). Diese Ports müssen direkt vom Browser der Teilnehmer:innen erreichbar sein – sie dürfen also nicht nur lokal gebunden sein und müssen in der Firewall bzw. beim Provider freigegeben werden.
- SFU_ANNOUNCED_IP: Die öffentlich erreichbare IP oder Domain, die in den WebRTC-Kandidaten (ICE) an die Clients ausgegeben wird – ohne korrekten Wert funktioniert keine Verbindung von außen.
Vorbereitung: Repo, .env & Platzhalter
MiroTalk SFU liest seine Konfiguration aus zwei Dateien, die per Volume schreibgeschützt in den Container gemountet werden: app/src/config.js (Standardwerte + Doku) und .env (Geheimnisse & Umgebungs-Overrides). Beide stammen aus dem Projekt-Repository – du musst es also einmal klonen:
git clone https://github.com/miroslavpejic85/mirotalksfu.git
cd mirotalksfu
cp .env.template .env
Danach die Platzhalter in der .env ersetzen – sonst können Gäste keine Verbindung aufbauen:
| Variable | Platzhalter | Setzen auf |
|---|---|---|
SFU_ANNOUNCED_IP | yourip | deine öffentliche IP oder Domain (für ICE-Kandidaten) |
SERVER_HOST_URL | https://talk.meine.domain | die öffentliche URL, unter der die Konferenz erreichbar ist |
SERVER_LISTEN_IP | yourip | die IP, auf der der Server lauscht (bei Docker: Host-IP bzw. 0.0.0.0) |
JWT_SECRET, RTMP_SECRET, API_KEY_SECRET | Standardwerte wie mirotalksfu | lange, zufällige Secrets (z. B. openssl rand -hex 32) |
HOST_USERS | Demo-Zugänge (admin:admin, guest:guest) | eigene Host-Nutzer:innen definieren (oder leeren) |
STATS_ENABLED | true + externe STATS_SRC | für volle Privatsphäre: false (lädt sonst ein Skript von stats.mirotalk.com) |
.env gehört in .gitignore und niemals ins Repository – sie enthält Secrets. Die Werte admin:admin und guest:guest in HOST_USERS sind Demo-Zugänge!Docker Compose – die Datei
Deine Vorlage, aufgeräumt und kommentiert:
services:
mirotalksfu:
image: mirotalk/sfu:latest
container_name: mirotalksfu
hostname: mirotalksfu
user: 1000:1000 # Host-User – für read-only-Configs unkritisch
restart: unless-stopped
volumes:
- ./app/src/config.js:/src/app/src/config.js:ro
- ./.env:/src/.env:ro
# - ./app/rec:/src/app/rec # nur wenn server.recording.enabled = true
# - ./app/rtmp:/src/app/rtmp # nur wenn server.rtmp.enabled.fromFile = true
ports:
- "127.0.0.1:3010:3010/tcp" # Web-UI & Signaling → Reverse Proxy
- "40000-40100:40000-40100/tcp" # WebRTC-Medien (öffentlich!)
- "40000-40100:40000-40100/udp" # WebRTC-Medien (öffentlich!)
networks: {}
- Config-Volumes:
config.jsund.envwerden schreibgeschützt gemountet – Änderungen wirken also erst nachdocker compose up -d --force-recreate. - Optional-Volumes: Aufzeichnung (
rec) und RTMP-Dateien (rtmp) nur einbinden, wenn du die Features inconfig.jsaktivierst. - Real-time-Updates: Die zusätzlich auskommentierten Mounts (
./app/:ro,./public/:ro) sind nur für die Entwicklung gedacht. - Netzwerk: Der leere
networks: {}-Block wurde entfernt – der Standard-Bridge-Modus genügt.
Die .env im Überblick
Die .env von MiroTalk SFU ist mit knapp 450 Zeilen sehr umfangreich, folgt aber einem klaren Schema: config.js liefert die Standardwerte, die .env überschreibt sie – so bleiben Secrets aus dem Code und Änderungen sind ohne Redeploy möglich. Die wichtigsten Gruppen aus deiner Datei:
| Bereich | Wichtige Variablen | Bedeutung |
|---|---|---|
| Core | NODE_ENV, SFU_ANNOUNCED_IP, SFU_MIN/MAX_PORT, SFU_NUM_WORKERS | Laufzeit, öffentliche IP, WebRTC-Portbereich, Worker-Anzahl |
| Server | SERVER_HOST_URL, SERVER_LISTEN_PORT, TRUST_PROXY, SSL-Zertifikatspfade | öffentliche URL, Port 3010, Proxy-Vertrauen, optional TLS direkt |
| Security | JWT_SECRET, HOST_PROTECTED, HOST_USERS, IP_WHITELIST_*, OIDC | Zugriffsschutz, Host-Rollen, Brute-Force-Limits, Single-Sign-On |
| Media | RECORDING_*, RTMP_* | serverseitige Aufzeichnung & Live-Streaming (beides bei dir deaktiviert) |
| UI | APP_NAME, UI_LANGUAGE, SHOW_*-Flags | Branding und sichtbare Buttons (Whiteboard, Umfragen, Aufzeichnung …) |
| Integrationen | CHATGPT_*, DEEP_SEEK_*, SLACK_*, MATTERMOST_*, DISCORD_* | KI-Assistenten im Chat und Chat-Plattform-Anbindung (alle standardmäßig aus) |
Deine .env als Vorlage
Hier die vollständige .env (unverändert übernommen) – als Vorlage zum Vergleichen und Anpassen:
# ====================================================
# MiroTalk SFU v.2.1.25 - Environment Configuration
# ====================================================
# config.js - Main configuration with:
# - All default values and documentation
# - Complex logic and validations
# - Safe to commit to version control
#
# .env - Environment overrides with:
# - Secrets and sensitive data (NEVER commit)
# - Environment-specific settings
# - Simple key=value format
#
# Why this works best:
# 1. SECURITY: Secrets stay out of codebase
# 2. FLEXIBILITY: Change settings without redeploy
# 3. SAFETY: Built-in defaults prevent crashes
# 4. DOCS: config.js explains all options
#
# Best practice:
# - Keep DEFAULT values in config.js
# - Keep SECRETS/ENV-SPECIFIC in .env
# - Add .env to .gitignore
# ====================================================
# ----------------------------------------------------
# 1. Core System Configuration
# ----------------------------------------------------
NODE_ENV=production # Runtime environment: development|production
SFU_ANNOUNCED_IP=yourip # Set your public IP address or domain for WebRTC announcements
SFU_LISTEN_IP=0.0.0.0 # Bind to all interfaces
SFU_MIN_PORT=40000 # Minimum WebRTC port range
SFU_MAX_PORT=40100 # Maximum WebRTC port range
SFU_NUM_WORKERS=8 # Number of worker processes (defaults to CPU count)
SFU_SERVER=false # Enable/disable WebRTC server (true|false)
# ----------------------------------------------------
# 2. Server Configuration
# ----------------------------------------------------
SERVER_HOST_URL=https://talk.meine.domain # Public server URL (e.g., https://yourdomain.com)
SERVER_LISTEN_IP=yourip # Server bind IP
SERVER_LISTEN_PORT=3010 # Port to listen on
TRUST_PROXY=false # Trust proxy headers (true in production behind reverse proxy)
SERVER_SSL_CERT=../ssl/cert.pem # Path to SSL certificate
SERVER_SSL_KEY=../ssl/key.pem # Path to SSL private key
CORS_ORIGIN=* # Allowed CORS origins (comma-separated)
# ----------------------------------------------------
# 3. Logging Configuration
# ----------------------------------------------------
LOGS_JSON=false # Log output in JSON format (true|false)
LOGS_JSON_PRETTY=false # Pretty-print JSON logs (true|false)
# ----------------------------------------------------
# 4. Media Handling
# ----------------------------------------------------
# Recording
RECORDING_ENABLED=false # Enable server-side recording (true|false)
RECORDING_DIR='../rec' # Directory to store recordings (Relative to app/src/)
RECORDING_MAX_FILE_SIZE=1073741824 # Max recording file size in bytes (default: 1GB)
RECORDING_UPLOAD_TO_S3=false # Upload recordings to S3-compatible storage (MinIO, Wasabi, DigitalOcean Spaces, etc.) (true|false)
RECORDING_ENDPOINT= # Recording service endpoint es http://localhost:8080
# Rtmp streaming
RTMP_ENABLED=false # Enable RTMP streaming (true|false)
RTMP_DIR='../rtmp' # Directory to read files for RTMP streams (Relative to app/src/)
RTMP_FROM_FILE=true # Enable local file streaming
RTMP_FROM_URL=true # Enable URL streaming
RTMP_FROM_STREAM=true # Enable live stream (camera, microphone, screen, window)
RTMP_MAX_STREAMS=1 # Max simultaneous RTMP streams
RTMP_USE_NODE_MEDIA_SERVER=true # Use Node Media Server mirotalk-nms Docker image
RTMP_SERVER=rtmp://localhost:1935 # RTMP server URL
RTMP_APP_NAME=live # RTMP application name
RTMP_STREAM_KEY= # RTMP stream key (optional)
RTMP_SECRET=mirotalk # RTMP API secret (Must match node-media-server config.js)
RTMP_API_SECRET=mirotalk # RTMP internal secret
RTMP_EXPIRATION_HOURS=4 # RTMP URL validity duration (hours)
RTMP_FFMPEG_PATH= # RTMP Custom path to FFmpeg binary (auto-detected if empty)
# ----------------------------------------------------
# 5. Security & Authentication
# ----------------------------------------------------
# Middleware
IP_WHITELIST_ENABLED=false # Restrict access by IP (true|false)
IP_WHITELIST_ALLOWED=127.0.0.1,::1 # Allowed IPs (comma-separated)
# Token
JWT_SECRET=mirotalksfu # Secret for JWT tokens
JWT_EXPIRATION=1h # JWT token expiration (e.g., 1h, 7d)
# OIDC
OIDC_ENABLED=false # Enable OpenID Connect (true|false)
OIDC_ALLOW_ROOMS_CREATION_FOR_AUTH_USERS=true # Allow all authenticated users via OIDC to create their own rooms
OIDC_ISSUER=https://server.example.com # OIDC provider URL
OIDC_BASE_URL= # OIDC base URL es https://yourdomain.com
OIDC_CLIENT_ID=clientID # OIDC client ID
OIDC_CLIENT_SECRET=clientSecret # OIDC client secret
OIDC_SECRET=mirotalksfu # OIDC secret
OIDC_AUTH_REQUIRED=false # set to true if authentication is required for all routes
OIDC_AUTH_LOGOUT=true # Controls automatic logout from both your app and Auth0 (true|false)
OIDC_USERNAME_FORCE=true # Force the username to match OIDC email or name (true|false)
OIDC_USERNAME_AS_EMAIL=true # Set username as email from OIDC (true|false)
OIDC_USERNAME_AS_NAME=false # Set username as name from OIDC (true|false)
# Host protection
HOST_PROTECTED=false # Enable host protection (true|false)
HOST_USER_AUTH=false # Enable user authentication (true|false)
# Host users - Define host users in the format: username:password:displayName:allowedRooms (room1,room2...)
HOST_USERS="username:password:User:*|admin:admin:Admin:room1,room2|guest:guest:Guest:room1,room2"
HOST_MAX_LOGIN_ATTEMPTS=5 # Maximum login attempts before temporary block
HOST_MIN_LOGIN_BLOCK_TIME=15 # Block duration in minutes after max attempts exceeded
# Endpoints
HOST_USERS_FROM_DB=false # Use DB for user auth (true|false)
USERS_API_SECRET=mirotalkweb # Users API secret key
USERS_API_ENDPOINT=http://localhost:9000/api/v1/user/isAuth # User auth endpoint
USERS_ROOM_ALLOWED_ENDPOINT=http://localhost:9000/api/v1/user/isRoomAllowed # Room permission endpoint
USERS_ROOMS_ALLOWED_ENDPOINT=http://localhost:9000/api/v1/user/roomsAllowed # Allowed rooms endpoint
ROOM_EXISTS_ENDPOINT=http://localhost:9000/api/v1/room/exists # Room exists endpoint
# Presenters
PRESENTERS=Miroslav Pejic,miroslav.pejic.85@gmail.com, # Presenter usernames (comma-separated)
PRESENTER_JOIN_FIRST=true # First joiner becomes presenter (true|false)
# ----------------------------------------------------
# 6. API Configuration
# ----------------------------------------------------
API_KEY_SECRET=mirotalksfu
API_ALLOW_STATS=true # Enable stats API (true|false)
API_ALLOW_MEETINGS=false # Allow meetings API endpoint (true|false)
API_ALLOW_MEETING=true # Allow single meeting API endpoint (true|false)
API_ALLOW_MEETING_END=false # Allow meeting end API endpoint (true|false)
API_ALLOW_JOIN=true # Allow join API endpoint (true|false)
API_ALLOW_TOKEN=false # Allow token-based API authentication (true|false)
API_ALLOW_SLACK=true # Allow Slack integration via API (true|false)
API_ALLOW_MATTERMOST=true # Allow Mattermost integration via API (true|false)
# ----------------------------------------------------
# 7. Third-Party Integrations
# ----------------------------------------------------
# ChatGPT Integration
CHATGPT_ENABLED=false # Enable ChatGPT integration (true|false)
CHATGPT_BASE_PATH=https://api.openai.com/v1/ # OpenAI base path
CHATGPT_API_KEY= # OpenAI API key
CHATGPT_MODEL=gpt-3.5-turbo # Model to use (gpt-3.5-turbo, gpt-4, etc.)
CHATGPT_MAX_TOKENS=1024 # Max response tokens
CHATGPT_TEMPERATURE=0.7 # Creativity level (0-1)
# DeepSeek Integration
DEEP_SEEK_ENABLED=false # Enable DeepSeek integration (true|false)
DEEP_SEEK_BASE_PATH=https://api.deepseek.com/v1/ # DeepSeek base path
DEEP_SEEK_API_KEY= # DeepSeek API key
DEEP_SEEK_MODEL=deepseek-chat # Model to use (deepseek-chat, deepseek-coder, etc.)
DEEP_SEEK_MAX_TOKENS=1024 # Max response tokens
DEEP_SEEK_TEMPERATURE=0.7 # Creativity level (0-1)
# Video AI (HeyGen) Integration
VIDEOAI_ENABLED=false # Enable video AI avatars (true|false)
VIDEOAI_API_KEY= # HeyGen API key
VIDEOAI_SYSTEM_LIMIT= # AI system prompt
# Email Alerts
EMAIL_ALERTS_ENABLED=false # Enable email alerts (true|false)
EMAIL_NOTIFICATIONS=false # Enable email notifications (true|false)
EMAIL_HOST=smtp.gmail.com # SMTP server host
EMAIL_PORT=587 # SMTP port
EMAIL_USERNAME=your_username # SMTP username
EMAIL_PASSWORD=your_password # SMTP password
EMAIL_FROM= # Sender email address
EMAIL_SEND_TO=sfu.mirotalk@gmail.com # Notification recipient
# Slack Integration
SLACK_ENABLED=false # Enable Slack integration (true|false)
SLACK_SIGNING_SECRET= # Slack app signing secret
# Mattermost Integration
MATTERMOST_ENABLED=false # Enable Mattermost (true|false)
MATTERMOST_SERVER_URL=YourMattermostServerUrl # Mattermost server URL
MATTERMOST_USERNAME=YourMattermostUsername # Mattermost username
MATTERMOST_PASSWORD=YourMattermostPassword # Mattermost password
MATTERMOST_TOKEN=YourMattermostToken # Mattermost slash command token
MATTERMOST_COMMAND_NAME=/sfu # Mattermost command name
MATTERMOST_DEFAULT_MESSAGE=Here is your meeting room: # Mattermost default message
# Discord Integration
DISCORD_ENABLED=false # Enable Discord bot (true|false)
DISCORD_TOKEN= # Discord bot token
DISCORD_COMMAND_NAME=/sfu # Discord command name
DISCORD_DEFAULT_MESSAGE=Here is your SFU meeting room: # Discord default message
DISCORD_BASE_URL=https://sfu.mirotalk.com/join/ # Discord Base URL for meeting rooms
# Ngrok Tunnel
NGROK_ENABLED=false # Enable Ngrok tunneling (true|false)
NGROK_AUTH_TOKEN= # Ngrok authentication token
# Sentry Error Tracking
SENTRY_ENABLED=false # Enable Sentry error tracking (true|false)
SENTRY_LOG_LEVELS=error # Log levels to capture (comma-separated)
SENTRY_DSN= # Sentry DSN URL
SENTRY_TRACES_SAMPLE_RATE=0.2 # Error sampling rate (0-1)
# Webhook Notifications
WEBHOOK_ENABLED=false # Enable webhook notifications (true|false)
WEBHOOK_URL=https://your-site.com/webhook-endpoint # Webhook endpoint URL
# IP Geolocation
IP_LOOKUP_ENABLED=false # Enable IP lookup functionality (true|false)
# S3-Compatible Object Storage Configuration
S3_ENABLED=false # Enable S3-compatible storage (true|false)
S3_ACCESS_KEY_ID= # Access Key ID (leave empty if using instance credentials or IAM roles)
S3_SECRET_ACCESS_KEY= # Secret Access Key (leave empty if using instance credentials or IAM roles)
S3_BUCKET=mirotalk # Name of your storage bucket (must exist)
S3_REGION= # Region or location (e.g., us-east-2, eu-west-2, or custom for non-AWS providers)
S3_ENDPOINT= # Custom endpoint URL for S3-compatible services (leave empty to auto-resolve from region). Use for MinIO, Wasabi, DigitalOcean Spaces, etc.
S3_FORCE_PATH_STYLE=false # Use path-style URLs (true|false). Set to true for most S3-compatible providers (MinIO, Wasabi, etc.)
# ----------------------------------------------------
# 8. UI Customization
# ----------------------------------------------------
# Branding injection
BRAND_HTML_INJECTION=true # Enable HTML injection for branding (true|false)
# Widget Configuration
WIDGET_ENABLED=false # Enable branding widget (true|false)
WIDGET_ROOM_ID=support-room # Widget room ID (default: support-room)
WIDGET_THEME=dark # Widget theme (dark|light)
WIDGET_STATE=minimized # Widget initial state (normal|minimized|closed)
WIDGET_TYPE=support # Widget type (support)
WIDGET_SUPPORT_POSITION=top-right # Support widget position (top-right|top-left|bottom-right|bottom-left)
WIDGET_SUPPORT_EXPERT_IMAGES= # Comma-separated expert image URLs comma-separated
WIDGET_SUPPORT_BUTTON_AUDIO=true # Show audio button in support widget (false|false)
WIDGET_SUPPORT_BUTTON_VIDEO=true # Show video button in support widget (false|false)
WIDGET_SUPPORT_BUTTON_SCREEN=true # Show screen share button in support widget (false|false)
WIDGET_SUPPORT_BUTTON_CHAT=true # Show chat button in support widget (false|false)
WIDGET_SUPPORT_BUTTON_JOIN=true # Show join button in support widget (false|false)
WIDGET_SUPPORT_CHECK_ONLINE_STATUS=false # Check online room status (true|false)
WIDGET_SUPPORT_IS_ONLINE=true # Is support online (true|false)
WIDGET_SUPPORT_HEADING=Need Help? # Support widget heading
WIDGET_SUPPORT_SUBHEADING=Get instant support from our expert team! # Support widget subheading
WIDGET_SUPPORT_CONNECT_TEXT=connect in < 5 seconds # Connect text
WIDGET_SUPPORT_ONLINE_TEXT=We are online # Online text
WIDGET_SUPPORT_OFFLINE_TEXT=We are offline # Offline text
WIDGET_SUPPORT_POWERED_BY=Powered by MiroTalk SFU # Powered by text
# Widget Alert Notifications
WIDGET_ALERT_ENABLED=false # Enable alert notifications (true|false)
WIDGET_ALERT_TYPE=email # Alert notification type (email) need EMAIL_ALERTS_ENABLED=true and configuration
# App
UI_LANGUAGE=en # Interface language (en, es, fr, etc.)
APP_NAME=Talk # Application name
APP_TITLE= # Custom HTML title (leave empty for default)
APP_DESCRIPTION= # Application description
JOIN_DESCRIPTION= # Join screen description
JOIN_BUTTON_LABEL=JOIN ROOM # Join button text
CUSTOMIZE_BUTTON_LABEL=CUSTOMIZE ROOM # Customize button text
JOIN_LAST_LABEL=Your recent room: # Recent room label text
# Site
SITE_TITLE=Talk # Website title
SITE_ICON_PATH=../images/logo.svg # Favicon path
APPLE_TOUCH_ICON_PATH=../images/logo.svg # Apple touch icon path
NEW_ROOM_TITLE= # New room title
NEW_ROOM_DESC= # New room description
# Meta
META_DESCRIPTION= # HTML meta description
META_KEYWORDS= # HTML meta keywords
# OG
OG_TYPE=app-webrtc # OpenGraph type
OG_SITE_NAME=Talk # OG site name
OG_TITLE= # OG title
OG_DESCRIPTION= # OG description
OG_IMAGE_URL=https://sfu.mirotalk.com/images/mirotalksfu.png # OG image
OG_URL=https://sfu.mirotalk.com # OG URL
# HTML
SHOW_TOP_SPONSORS=false # Show top sponsors section (true|false)
SHOW_FEATURES=true # Show features section (true|false)
SHOW_TEAMS=false # Show teams section (true|false)
SHOW_TRY_EASIER=true # Show "try easier" section (true|false)
SHOW_POWERED_BY=false # Show powered by (true|false)
SHOW_SPONSORS=false # Show sponsors (true|false)
SHOW_PAST_SPONSORS=false # Show past sponsors (true|false)
SHOW_ADVERTISERS=false # Show advertisers (true|false)
SHOW_SUPPORT_US=false # Show support us section (true|false)
SHOW_FOOTER=true # Show footer (true|false)
# Who Are You
WHO_ARE_YOU_TITLE="Who are you?" # Title
WHO_ARE_YOU_DESCRIPTION="If you're the presenter, please log in now.<br />Otherwise, kindly wait for the presenter to join." # Description
WHO_ARE_YOU_BUTTON_LOGIN_LABEL="LOGIN" # Login button label
WHO_ARE_YOU_JOIN_LABEL="JOIN ROOM" # Join button label
# About
ABOUT_IMAGE_URL=../images/mirotalk-logo.gif # About section image
SUPPORT_URL=https://codecanyon.net/user/miroslavpejic85 # Support link URL
SUPPORT_TEXT=Support # Support button text
AUTHOR_LABEL=Author # Author label text
AUTHOR_NAME=Miroslav Pejic # Author name
LINKEDIN_URL=https://www.linkedin.com/in/miroslav-pejic-976a07101/ # LinkedIn profile
CONTACT_EMAIL=miroslav.pejic.85@gmail.com # Contact email
EMAIL_SUBJECT=MiroTalk SFU info # Email subject
COPYRIGHT_TEXT=MiroTalk SFU, all rights reserved # Copyright text
# ----------------------------------------------------
# 9. UI Button Configuration
# ----------------------------------------------------
# Active Rooms
SHOW_ACTIVE_ROOMS=false # Show active rooms feature (true|false)
# Popup Configuration
SHOW_SHARE_ROOM_POPUP=true # Show share room popup (true|false)
SHOW_SHARE_ROOM_QR_ON_HOVER=true # Show share room QR popup on mouse hover (true|false)
# Main Control Buttons
SHOW_AUDIO_BUTTON=true # Show audio button (true|false)
SHOW_VIDEO_BUTTON=true # Show video button (true|false)
SHOW_SCREEN_BUTTON=true # Show screen share button (true|false)
SHOW_SWAP_CAMERA=true # Show camera swap button (true|false)
SHOW_RAISE_HAND=true # Show raise hand button (true|false)
SHOW_CHAT_BUTTON=true # Show chat button (true|false)
SHOW_PARTICIPANTS_BUTTON=true # Show participants button (true|false)
SHOW_SETTINGS=true # Show settings button (true|false)
SHOW_EXTRA_BUTTON=true # Show extra buttons (true|false)
SHOW_EXIT_BUTTON=true # Show exit button (true|false)
# Settings Extra Buttons
SHOW_SHARE_BUTTON=true # Show share button (true|false)
SHOW_HIDE_ME=true # Show hide me button (true|false)
SHOW_FULLSCREEN_BUTTON=true # Show fullscreen button (true|false)
SHOW_EMOJI=true # Show emoji button (true|false)
SHOW_TRANSCRIPTION=true # Show transcription button (true|false)
SHOW_POLL_BUTTON=true # Show poll button (true|false)
SHOW_EDITOR_BUTTON=true # Show editor button (true|false)
SHOW_WHITEBOARD=true # Show whiteboard button (true|false)
SHOW_DOCUMENT_PIP=true # Show document PiP button (true|false)
SHOW_SNAPSHOT=true # Show snapshot button (true|false)
SHOW_ABOUT=true # Show about button (true|false)
# Settings Panel
SHOW_ROOMS=true # Show rooms (true|false)
ENABLE_FILE_SHARING=true # Enable file sharing (true|false)
SHOW_LOCK_ROOM=true # Show lock room button (true|false)
SHOW_UNLOCK_ROOM=true # Show unlock room button (true|false)
SHOW_BROADCASTING=true # Show broadcasting button (true|false)
SHOW_LOBBY=true # Show lobby button (true|false)
SHOW_EMAIL_INVITE=true # Show email invitation button (true|false)
SHOW_MIC_OPTIONS=true # Show mic options button (true|false)
SHOW_RTMP_TAB=true # Show RTMP tab (true|false)
SHOW_NOTIFICATIONS_TAB=true # Show notifications tab (true|false)
SHOW_MODERATOR_TAB=true # Show moderator tab (true|false)
SHOW_RECORDING_TAB=true # Show recording tab (true|false)
HOST_ONLY_RECORDING=true # Only host can record (true|false)
ENABLE_PUSH_TO_TALK=true # Enable push-to-talk (true|false)
SHOW_KEYBOARD_SHORTCUTS=true # Show keyboard shortcuts (true|false)
SHOW_VIRTUAL_BACKGROUND=true # Show virtual background (true|false)
# Video Controls
ENABLE_PIP=true # Enable picture-in-picture (true|false)
SHOW_MIRROR_BUTTON=true # Show mirror button (true|false)
SHOW_FULLSCREEN=true # Show fullscreen button (true|false)
SHOW_SNAPSHOT_BUTTON=true # Show snapshot button (true|false)
SHOW_MUTE_AUDIO=true # Show mute audio button (true|false)
SHOW_PRIVACY_TOGGLE=true # Show privacy toggle (true|false)
SHOW_VOLUME_CONTROL=true # Show volume control (true|false)
SHOW_FOCUS_BUTTON=true # Show focus button (true|false)
SHOW_SEND_MESSAGE=true # Show send message button (true|false)
SHOW_SEND_FILE=true # Show send file button (true|false)
SHOW_SEND_VIDEO=true # Show send video button (true|false)
SHOW_MUTE_VIDEO=true # Show mute video button (true|false)
SHOW_GEO_LOCATION=true # Show geoLocation button (true|false)
SHOW_BAN_BUTTON=true # Show ban button (true|false)
SHOW_EJECT_BUTTON=true # Show eject button (true|false)
# Chat Controls
SHOW_CHAT_PIN=true # Show chat pin button (true|false)
SHOW_CHAT_MAXIMIZE=true # Show chat maximize button (true|false)
SHOW_CHAT_SAVE=true # Show chat save button (true|false)
SHOW_CHAT_EMOJI=true # Show chat emoji button (true|false)
SHOW_CHAT_MARKDOWN=true # Show chat markdown button (true|false)
SHOW_CHAT_SPEECH=true # Show chat speech button (true|false)
ENABLE_CHAT_GPT=true # Enable ChatGPT in chat (true|false)
ENABLE_DEEP_SEEK=true # Enable DeepSeek in chat (true|false)
# Poll Controls
SHOW_POLL_PIN=true # Show poll pin button (true|false)
SHOW_POLL_MAXIMIZE=true # Show poll maximize button (true|false)
SHOW_POLL_SAVE=true # Show poll save button (true|false)
# Participants Controls
SHOW_SAVE_INFO=true # Show save info button (true|false)
SHOW_SEND_FILE_ALL=true # Show send file to all button (true|false)
SHOW_EJECT_ALL=true # Show eject all button (true|false)
# Whiteboard Controls
SHOW_WB_LOCK=true # Show whiteboard lock button (true|false)
# ----------------------------------------------------
# 10. Feature Flags
# ----------------------------------------------------
SURVEY_ENABLED=false # Enable post-call survey (true|false)
SURVEY_URL= # Survey URL
# Redirect
REDIRECT_ENABLED=false # Enable post-call redirect (true|false)
REDIRECT_URL= # Redirect URL
# Stats
STATS_ENABLED=true # Enable usage statistics (true|false)
STATS_SRC=https://stats.mirotalk.com/script.js # Stats script URL
STATS_ID=41d26670-f275-45bb-af82-3ce91iko5 # Stats tracking ID
# Custom noise suppression
CUSTOM_NOISE_SUPPRESSION_ENABLED=true # Enable custom noise suppression, default one will still work if this is disabled (true|false)
# -----------------------------------------------------
# 11. Global Moderation Configuration
# -----------------------------------------------------
ROOM_MAX_PARTICIPANTS=1000 # Maximum participants per room
ROOM_LOBBY=false # Enable room lobby (true|false)
# ----------------------------------------------------
# 12. Mediasoup Configuration
# ----------------------------------------------------
MEDIASOUP_ROUTER_AUDIO_LEVEL_OBSERVER_ENABLED=true # Enable audio level observer (true|false)
MEDIASOUP_ROUTER_ACTIVE_SPEAKER_OBSERVER_ENABLED=false # Enable active speaker observer (true|false)
MEDIASOUP_LOG_LEVEL=error # Mediasoup log level (debug, warn, error)
Reverse Proxy, WebSockets & Firewall
Die Web-UI (3010) wird nur lokal gebunden – für HTTPS nach außen brauchst du einen Reverse Proxy. Wichtig: MiroTalk SFU nutzt WebSockets fürs Signaling – der Proxy muss Upgrade-Header durchreichen und lange Lese-Timeouts setzen. Außerdem TRUST_PROXY=true in der .env setzen, damit hinter dem Proxy die echten Client-IPs ankommen:
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name talk.meine.domain;
access_log off;
error_log /var/log/nginx/talk.meine.domain.error.log;
# ---------------------------------------------------------------- Zertifikat (ECDSA)
ssl_certificate /etc/ssl/private/talk.meine.domain_ecc/fullchain.cer;
ssl_certificate_key /etc/ssl/private/talk.meine.domain_ecc/talk.meine.domain.key;
# ---------------------------------------------------------------- TLS-Feinschliff
ssl_buffer_size 1400; # passt gut zu 1500-Byte-Ethernet-MTU
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off; # Session-Tickets aus: bessere Forward Secrecy
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off; # TLS 1.3 entscheidet der Client; aktueller Rat
ssl_stapling on;
ssl_stapling_verify on;
ssl_ecdh_curve X25519:P-384:P-256:P-521;
# OPTIMIERUNG 2: Lokalen Resolver (oder Quad9/Cloudflare) eintragen für besseren Datenschutz als Google
resolver 9.9.9.9 1.1.1.1 valid=300s;
resolver_timeout 5s;
# Globale Sicherheits-Header (Inklusive Hardware-Rechte für Video/Audio)
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "same-origin" always;
add_header Permissions-Policy "accelerometer=(), autoplay=*, camera=(self), display-capture=(self), encrypted-media=(), fullscreen=*, geolocation=(), gyroscope=(), magnetometer=(), microphone=(self), midi=(), payment=(), publickey-credentials-get=(), speaker-selection=(self), usb=(), xr-spatial-tracking=()" always;
proxy_cookie_path / "/; HTTPOnly; Secure; SameSite=Lax";
keepalive_timeout 70;
sendfile on;
client_max_body_size 0;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
# MIROTALK-OPTIMIERTE HEADERS FÜR DAS A-RATING:
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "same-origin" always;
add_header Permissions-Policy "accelerometer=(), autoplay=*, camera=(self), display-capture=(self), encrypted-media=(), fullscreen=*, geolocation=(), gyroscope=(), magnetometer=(), microphone=(self), midi=(), payment=(), publickey-credentials-get=(), speaker-selection=(self), usb=(), xr-spatial-tracking=()" always;
add_header X-Frame-Options "SAMEORIGIN" always;
# MIROTALK-CSP: CDN-Freigaben hinzugefügt (Löst die Ladefehler, behält das A-Rating)
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://stats.mirotalk.com https://translate.google.com https://translate.googleapis.com https://unpkg.com https://buttons.github.io; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://www.w3schools.com https://translate.googleapis.com https://fonts.googleapis.com; img-src 'self' data: blob: https:; font-src 'self' data: https://cdnjs.cloudflare.com https://gstatic.com; connect-src 'self' wss: ws: blob: data: https://translate.googleapis.com; frame-ancestors 'self'; upgrade-insecure-requests;" always;
# CSP ermöglicht Inline-Skripte für das Node-Frontend und WebSockets für die Streams
# add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' wss: ws: blob: data:; frame-ancestors 'self'; upgrade-insecure-requests;" always;
# BEREINIGUNG: Verhindert doppelte Header aus dem Mirotalk Node.js-Backend
proxy_hide_header Strict-Transport-Security;
proxy_hide_header X-Content-Type-Options;
proxy_hide_header X-Frame-Options;
proxy_hide_header Content-Security-Policy;
proxy_hide_header Permissions-Policy;
# WebSockets für die Echtzeitübertragung
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Hohe Timeouts für stabile Video-Sitzungen
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_buffering off;
proxy_pass http://localhost:3010/;
}
}
Firewall & Port-Weiterleitung: Neben 443 (HTTPS) müssen auf dem Host TCP und UDP 40000–40100 eingehend erlaubt sein – und falls der Server hinter NAT steht, müssen diese Ports am Router weitergeleitet werden. SFU_ANNOUNCED_IP muss dabei auf die Adresse zeigen, unter der die Clients den Server erreichen (Domain, die auf die öffentliche IP zeigt, funktioniert ebenfalls).
Erster Start & erster Raum
docker compose up -d
docker compose ps
docker compose logs -f mirotalksfu
- Im Browser
https://talk.meine.domainöffnen (bzw. lokalhttp://127.0.0.1:3010). - Einen Raum erstellen (z. B.
/join/mein-team) und den Link an die Teilnehmer:innen schicken. - Zweiten Browser-Tab öffnen und beitreten – so testest du Audio/Video, bevor du echte Gäste einlädst.
- Die Logs zeigen dir, ob die Mediasoup-Worker starten und ob Verbindungen aufgebaut werden.
ufw status bzw. Provider-Dashboard) und ob SFU_ANNOUNCED_IP korrekt gesetzt ist.Updates, Backups & Betrieb
- Backup = Konfiguration: MiroTalk SFU speichert keine Nutzerdaten – gesichert werden
.envundapp/src/config.js(bzw. ein Diff gegen das Repo). Optional: Aufzeichnungen aus./app/rec. - Updates:
docker compose pull && docker compose up -d– danach immer einen Raumtest machen, da sichconfig.js-Standardwerte zwischen Versionen ändern können. - Watchtower-Vorsicht: Ein automatischer Neustart mitten in einer Konferenz beendet alle laufenden Räume – MiroTalk SFU per Label von Watchtower ausnehmen oder Updates im Wartungsfenster fahren.
- Ressourcen: Mit 8 Worker-Prozessen und mehreren parallelen Konferenzen ist RAM der Engpass – beobachte die Auslastung (z. B. mit Uptime Kuma + Docker stats).
Häufige Probleme (FAQ)
| Problem | Lösung |
|---|---|
| Kein Bild/Ton von außen | UDP/TCP 40000–40100 öffnen (Host-Firewall + Router/Provider) und SFU_ANNOUNCED_IP prüfen. |
| Verbindung bricht ab / „Connection failed“ | WebSocket-Upgrade im Proxy prüfen und TRUST_PROXY=true setzen. |
| Host-Login schlägt fehl | HOST_USERS in der .env prüfen – Demo-Zugänge admin:admin/guest:guest ersetzen. |
| Raum über eigenen Link nicht erreichbar | SERVER_HOST_URL muss exakt der öffentlichen URL entsprechen (Schema + Domain). |
| „Permission denied“ auf Config-Dateien | user: 1000:1000 prüfen – die .env/config.js müssen für den Container-User lesbar sein. |
| Nach Update startet nichts | docker compose up -d --force-recreate ausführen – Volumes sind read-only und werden sonst evtl. nicht neu eingelesen. |
Fazit
MiroTalk SFU ist eine der ausgewachsensten Self-Hosted-Konferenz-Lösungen: browserbasiert, mit Mediasoup im Kern und einem Konfigurationsumfang, der von reinen Video-Calls bis zu Whiteboard, Aufzeichnung, RTMP und KI-Integrationen reicht. Der Preis dafür ist ein Setup, das Netzwerk-Grundwissen fordert – die WebRTC-Portbereiche und die korrekte Ankündigungs-IP sind der Schlüssel zum Erfolg.
SFU_ANNOUNCED_IP und SERVER_HOST_URL müssen stimmen – sonst keine Verbindung. ③ Secrets & Demo-Host-Zugänge ersetzen, Statistik-Skript deaktivieren. ④ Backup = .env + config.js, nicht der Container.