// Tutorial · Self-Hosting · Kommunikation

MiroTalk SFU selbst hosten: Video-Konferenzen auf eigenen Servern

📅 06.09.2026 ⏱12 Min. Lesezeit ✍️ Redaktion

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.

Funktionsumfang: Audio/Video in HD, Bildschirm teilen, Chat, Dateiteilung, Whiteboard, Editor, Umfragen, virtuelle Hintergründe, „Raise Hand“, Aufzeichnung und RTMP-Streaming (optional) sowie API-Integrationen für Slack, Mattermost und Discord.

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.1 und 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.
Häufigster Fehler: Port 3010 erreichbar, aber die Medien-Ports 40000–40100/UDP sind zu – dann bleibt das Bild schwarz oder der Anruf bricht ab. Die Ports müssen auf dem Host und in der Firewall/am Router offen sein.

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:

VariablePlatzhalterSetzen auf
SFU_ANNOUNCED_IPyouripdeine öffentliche IP oder Domain (für ICE-Kandidaten)
SERVER_HOST_URLhttps://talk.meine.domaindie öffentliche URL, unter der die Konferenz erreichbar ist
SERVER_LISTEN_IPyouripdie IP, auf der der Server lauscht (bei Docker: Host-IP bzw. 0.0.0.0)
JWT_SECRET, RTMP_SECRET, API_KEY_SECRETStandardwerte wie mirotalksfulange, zufällige Secrets (z. B. openssl rand -hex 32)
HOST_USERSDemo-Zugänge (admin:admin, guest:guest)eigene Host-Nutzer:innen definieren (oder leeren)
STATS_ENABLEDtrue + externe STATS_SRCfür volle Privatsphäre: false (lädt sonst ein Skript von stats.mirotalk.com)
Merke: Die .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.js und .env werden schreibgeschützt gemountet – Änderungen wirken also erst nach docker compose up -d --force-recreate.
  • Optional-Volumes: Aufzeichnung (rec) und RTMP-Dateien (rtmp) nur einbinden, wenn du die Features in config.js aktivierst.
  • 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:

BereichWichtige VariablenBedeutung
CoreNODE_ENV, SFU_ANNOUNCED_IP, SFU_MIN/MAX_PORT, SFU_NUM_WORKERSLaufzeit, öffentliche IP, WebRTC-Portbereich, Worker-Anzahl
ServerSERVER_HOST_URL, SERVER_LISTEN_PORT, TRUST_PROXY, SSL-Zertifikatspfadeöffentliche URL, Port 3010, Proxy-Vertrauen, optional TLS direkt
SecurityJWT_SECRET, HOST_PROTECTED, HOST_USERS, IP_WHITELIST_*, OIDCZugriffsschutz, Host-Rollen, Brute-Force-Limits, Single-Sign-On
MediaRECORDING_*, RTMP_*serverseitige Aufzeichnung & Live-Streaming (beides bei dir deaktiviert)
UIAPP_NAME, UI_LANGUAGE, SHOW_*-FlagsBranding und sichtbare Buttons (Whiteboard, Umfragen, Aufzeichnung …)
IntegrationenCHATGPT_*, DEEP_SEEK_*, SLACK_*, MATTERMOST_*, DISCORD_*KI-Assistenten im Chat und Chat-Plattform-Anbindung (alle standardmäßig aus)
Grundregel: Was du nicht brauchst, bleibt aus – alle Integrationen und Features sind standardmäßig deaktiviert und machen die Instanz schlank und sicher.

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. lokal http://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.
Erst testen, dann einladen: Wenn Bild/Ton nicht ankommen, prüfe zuerst UDP 40000–40100 (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 .env und app/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 sich config.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)

ProblemLösung
Kein Bild/Ton von außenUDP/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 fehlHOST_USERS in der .env prüfen – Demo-Zugänge admin:admin/guest:guest ersetzen.
Raum über eigenen Link nicht erreichbarSERVER_HOST_URL muss exakt der öffentlichen URL entsprechen (Schema + Domain).
„Permission denied“ auf Config-Dateienuser: 1000:1000 prüfen – die .env/config.js müssen für den Container-User lesbar sein.
Nach Update startet nichtsdocker 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.

Merke: ① Web-UI (3010) hinter den Proxy, Medien-Ports (40000–40100) öffentlich. ② 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.
R
...

Wir betreiben unsere komplette Infrastruktur selbst auf Open-Source-Software und schreiben nur über Tools, die wir im Alltag wirklich einsetzen. Fragen zum Artikel? Schreib uns.