MiroTalk SFU - Self Hosting

Description
MiroTalk SFU is a scalable WebRTC solution for multi-party calls, using a Selective Forwarding Unit to forward streams efficiently and reduce bandwidth. Perfect for team meetings, webinars, and online classrooms.
Live demo: https://sfu.mirotalk.com
Requirements
- Server Selection:
- OS: Ubuntu 22.04 LTS.
- Node.js (LTS) and npm
- FFmpeg for optional RTMP streaming support.
- Domain or Subdomain Name (e.g.,
YOUR.DOMAIN.NAME) with a DNS A record pointing to your server's IPv4 address.
Automated Setup
Quick Automated Installation
You can automate the entire setup (Node.js, Nginx, SSL, Docker) using our installation scripts on a clean Ubuntu server. Check out the MiroTalk SFU Setup Script for details.
Installation
Note
Many of the installation steps require root or sudo access
# Run all apt commands in non-interactive mode (no prompts)
export DEBIAN_FRONTEND=noninteractive
# Update package lists
apt-get update -y
# Install required system packages
apt-get install -y \
build-essential \
git \
curl \
wget \
unzip \
tzdata \
software-properties-common \
ffmpeg
# Install Python 3.8 and pip
add-apt-repository -y ppa:deadsnakes/ppa
apt-get update -y
apt-get install -y python3.8 python3-pip

Install NodeJS and npm using Node Version Manager
Quick start
# Clone the repository
git clone https://github.com/miroslavpejic85/mirotalksfu.git
# Navigate to the project directory
cd mirotalksfu
# Copy the config template
cp app/src/config.template.js app/src/config.js
# Copy the environment template
cp .env.template .env
Edit .env Configuration
Edit .env now (see section below) before running npm install or npm start.
ENVIRONMENT=production
SFU_ANNOUNCED_IP=Your-Server-Public-IPv4-or-Domain
SFU_MIN_PORT=40000
SFU_MAX_PORT=40100 # With 40000-40100 (~100 ports), supports about ~50 participants
SFU_NUM_WORKERS=4 # Optional: if unset, defaults to CPU core count (`nproc`)
| Setting | What it does |
|---|---|
ENVIRONMENT |
Use production for server deployments. Enables production-ready behavior including proper HTTPS and WebRTC configuration. |
SFU_ANNOUNCED_IP |
Public IPv4 address or domain name announced to clients for ICE/WebRTC connectivity. Required when the server is behind NAT, Docker, or cloud infrastructure. |
SFU_NUM_WORKERS |
Number of parallel media workers. Typically set to the number of CPU cores for best performance. As a rough estimate, 1 worker can handle ~100 participants depending on video quality, bandwidth, and system load. |
SFU_MIN_PORT / SFU_MAX_PORT |
UDP port range used for WebRTC media traffic. Each participant consumes multiple dynamic ports for audio/video streams. A larger range allows more concurrent participants and connections. For reduced port exposure and easier scaling, you can enable SFU_SERVER=true (WebRTCServer mode). |
Firewall
If ports 40000-40100 are blocked, SFU media will fail. Set inbound rules as needed.
| Port range | Protocol | Source | Description |
|---|---|---|---|
| 3010 | TCP | 0.0.0.0/0 | APP listen on TCP |
| 40000-40100 | TCP | 0.0.0.0/0 | RTC port ranges TCP |
| 40000-40100 | UDP | 0.0.0.0/0 | RTC port ranges UDP |
UFW
iptables
# Allow established traffic and loopback
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
# Keep SSH open first (avoid lockout)
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# App + web ports
iptables -A INPUT -p tcp --dport 3010 -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# SFU media ports
iptables -A INPUT -p tcp --dport 40000:40100 -j ACCEPT
iptables -A INPUT -p udp --dport 40000:40100 -j ACCEPT
# Default policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Save rules (Ubuntu/Debian)
apt-get install -y iptables-persistent
netfilter-persistent save
WebRTCServer (optional)
You can activate the WebRTCServer option in the .env file:
Here's how it works:
- MiroTalk instantiates a
Workerfor eachCPU. - Each
Workerhas its ownWebRTCServer, which listens on a single port starting from40000. - This setup simplifies port management because you only need to open ports for the number of
Workersyou have.
Install dependencies and start the server
Verify the installation: http://YOUR.DOMAIN.NAME:3010
Using PM2 (Process Manager)

# Install PM2
npm install -g pm2
# Start the server
pm2 start app/src/Server.js --name mirotalksfu
# Save the process list
pm2 save
# Enable auto-start on boot
pm2 startup
Using Docker

# Install Docker and Docker Compose
sudo apt install -y docker.io
sudo apt install -y docker-compose
# Clone the repository
git clone https://github.com/miroslavpejic85/mirotalksfu.git
# Navigate to the project directory
cd mirotalksfu
# Copy and customize the config template
cp app/src/config.template.js app/src/config.js
# Copy and customize the environment template
cp .env.template .env
# Copy and customize the Docker Compose template
cp docker-compose.template.yml docker-compose.yml
Example of docker-compose.yml:
services:
mirotalksfu:
image: mirotalk/sfu:latest
container_name: mirotalksfu
hostname: mirotalksfu
restart: unless-stopped
network_mode: 'host'
volumes:
- ./.env:/src/.env:ro
- ./app/:/src/app/:ro
- ./public/:/src/public/:ro
# Pull the official Docker image
docker-compose pull
# Create and start containers (add -d to run in background)
docker-compose up
Verify the installation: http://YOUR.DOMAIN.NAME:3010
Configuring Nginx & Certbot

To use MiroTalk SFU without the port number and with encrypted communications (required for WebRTC to work correctly), install Nginx and Certbot:
# Install Nginx
sudo apt-get install -y nginx
# Install Certbot (SSL certificates)
sudo apt install -y snapd
sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
# Configure Nginx
sudo vim /etc/nginx/sites-enabled/default
Add the following:
# HTTP — redirect all traffic to HTTPS
server {
listen 80;
listen [::]:80;
server_name YOUR.DOMAIN.NAME;
return 301 https://$host$request_uri;
}
# Test Nginx configuration
sudo nginx -t
# Enable HTTPS with Certbot (follow the prompts)
sudo certbot certonly --nginx
# Add Let's Encrypt configuration to Nginx
sudo vim /etc/nginx/sites-enabled/default
Add the following:
# MiroTalk SFU - HTTPS — proxy all requests to the Node app
server {
# Enable HTTP/2
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name YOUR.DOMAIN.NAME;
# Use the Let’s Encrypt certificates
ssl_certificate /etc/letsencrypt/live/YOUR.DOMAIN.NAME/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/YOUR.DOMAIN.NAME/privkey.pem;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://localhost:3010/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Optional: increase buffers to accommodate large headers/cookies.
# May be required with some OIDC / SSO providers (e.g. Azure AD/Entra,
# Keycloak with many group claims) whose tokens exceed nginx defaults.
proxy_buffer_size 128k;
proxy_buffers 8 256k;
proxy_busy_buffers_size 512k;
}
}
# Test Nginx configuration again
sudo nginx -t
# Restart Nginx
service nginx restart
service nginx status
# Set up auto-renewal for SSL certificates
sudo certbot renew --dry-run --cert-name YOUR.DOMAIN.NAME
# Show certificates
sudo certbot certificates
Verify your MiroTalk SFU instance: https://YOUR.DOMAIN.NAME
Apache Virtual Host (Alternative to Nginx)

If you prefer Apache, configure it with the equivalent settings provided in this guide.
# Install Apache with Certbot
apt install python3-certbot-apache -y
# Set up SSL
certbot --apache --non-interactive --agree-tos -d YOUR.DOMAIN.NAME -m your.email.address
# Edit the Apache site configuration
sudo vim /etc/apache2/sites-enabled/YOUR.DOMAIN.NAME.conf
Add the following:
# HTTP — redirect all traffic to HTTPS
<VirtualHost *:80>
ServerName YOUR.DOMAIN.NAME
Redirect permanent / https://YOUR.DOMAIN.NAME
</VirtualHost>
# MiroTalk SFU - HTTPS — proxy all requests to the Node app
<VirtualHost *:443>
ServerName YOUR.DOMAIN.NAME
# SSL Configuration
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/YOUR.DOMAIN.NAME/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/YOUR.DOMAIN.NAME/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
# Enable HTTP/2 support
Protocols h2 http/1.1
<Location />
# Proxy Configuration for Node.js App
ProxyPass http://localhost:3010/
ProxyPassReverse http://localhost:3010/
ProxyPreserveHost On
RequestHeader set X-Forwarded-For "%{REMOTE_ADDR}s"
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set Host "%{HTTP_HOST}s"
# Enable WebSocket proxy support for Socket.IO
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) ws://localhost:3010/socket.io/$1 [P,L]
# Adjust the WebSocket path according to your Socket.IO configuration
# For Socket.IO 3.x or higher, use /socket.io/?EIO=4&transport=websocket
</Location>
</VirtualHost>
# Check configuration
sudo apache2ctl configtest
sudo a2enmod proxy # Enables the `mod_proxy` module, which is essential for proxying HTTP and WebSocket connections.
sudo a2enmod proxy_http # Enables the `mod_proxy_http` module, which adds support for proxying HTTP connections.
sudo a2enmod proxy_wstunnel # Enables the `mod_proxy_wstunnel` module, which provides support for tunneling WebSocket connections
# Restart apache
sudo systemctl restart apache2
Updating Your Instance
To keep your MiroTalk SFU instance up to date, create an update script:
For PM2:
For Docker:
#!/bin/bash
cd mirotalksfu
git pull
docker-compose down
docker-compose pull
docker image prune -f
docker-compose up -d
Make the script executable
To update your MiroTalk SFU instance to the latest version, run the script:
Changelogs
Stay informed about project updates by following the commits of the MiroTalk SFU project here