← Back to posts
comparison guide self-hosted · · 11 min read

Janus Gateway vs Mediasoup vs LiveKit: Best Self-Hosted WebRTC SFU 2026

Compare Janus Gateway, Mediasoup, and LiveKit for self-hosted WebRTC SFU servers. Complete guide with Docker setups, architecture comparison, and deployment best practices for real-time communication.

OS
Editorial Team

Building a self-hosted real-time communication platform requires a Selective Forwarding Unit (SFU) to route audio and video streams between participants. Three projects dominate this space in 2026: Janus Gateway (9,077 stars, C), Mediasoup (7,227 stars, C++), and LiveKit (18,365 stars, Go).

This guide compares all three, covering architecture, deployment, scalability, and developer experience, so you can pick the right WebRTC server for your use case.

Why Self-Host Your WebRTC Infrastructure?

WebRTC enables peer-to-peer audio, video, and data channels directly in the browser. But when more than two people join a call, a server-side SFU becomes essential. Commercial WebRTC platforms (Agora, Twilio Video, Daily.co) charge per participant-minute — costs that scale linearly with usage.

Self-hosting your SFU gives you:

  • Cost control — no per-minute billing, only infrastructure costs
  • Data sovereignty — media streams never leave your servers
  • Custom features — full control over routing, recording, and processing
  • No vendor lock-in — open-source protocols and APIs
  • Compliance — meet GDPR, HIPAA, or internal data residency requirements

Whether you’re building a video conferencing app, a live streaming platform, a telehealth solution, or an online classroom, choosing the right SFU is the most critical infrastructure decision you’ll make.

Architecture Comparison

The three projects take fundamentally different approaches to WebRTC routing.

Janus Gateway: Plugin-Based MCU/SFU Hybrid

Janus is the oldest and most versatile of the three. Written in C by Meetecho, it operates as a generic WebRTC gateway with a plugin architecture. Each plugin implements a specific use case — video room, streaming, SIP gateway, echo test, and more.

Key architectural traits:

  • Plugin model — extend functionality without modifying core
  • Multiple roles — can act as SFU (selective forwarding) or MCU (mixing) depending on plugin
  • HTTP/JSON API — RESTful admin interface plus WebSockets for browser clients
  • Broad codec support — VP8, VP9, H.264, Opus, G.711, and more
  • SIP integration — built-in SIP plugin bridges WebRTC to traditional telephony

Mediasoup: High-Performance Node.js SFU Library

Mediasoup takes a different approach — it’s not a standalone server but a library you embed into your Node.js application. The C++ worker handles media processing while the JavaScript layer provides the API.

Key architectural traits:

  • Library, not server — embed into your own Node.js app
  • C++ worker processes — media handling runs in separate processes, one per CPU core
  • Transport abstraction — unified API for WebRTC, plain RTP, and plain RTP with RTCP
  • Fine-grained control — producers, consumers, and transports are individually managed
  • No built-in signaling — you implement your own signaling layer (WebSocket, HTTP, etc.)

LiveKit: Full-Stack Real-Time Platform

LiveKit is the most opinionated and complete solution. Written in Go, it provides a full server with built-in signaling, room management, and a comprehensive SDK ecosystem.

Key architectural traits:

  • Batteries included — server, signaling, SDKs for all platforms
  • Redis-backed clustering — horizontal scaling via Redis coordination
  • Egress/Ingress services — recording, streaming, and SIP trunking as separate services
  • Room-based model — participants join rooms, tracks are published/subscribed
  • JWT authentication — token-based access control built in
FeatureJanus GatewayMediasoupLiveKit
LanguageCC++ worker + Node.js APIGo
GitHub Stars9,0777,22718,365
ArchitecturePlugin-based SFU/MCUEmbedded SFU libraryFull-stack platform
Built-in SignalingYes (Admin API + WebSockets)No (implement your own)Yes (built-in)
ClusteringExternal (shared storage)Manual (application-level)Yes (Redis-backed)
RecordingVia recording pluginManual (capture from consumer)Egress service
SIP GatewayBuilt-in SIP pluginNoSIP service (separate)
SDK EcosystemJavaScript, minimalJavaScript-focusedGo, JS, iOS, Android, Flutter, React Native, Unity
Learning CurveMediumSteepLow
Best ForCustom telephony, experimentationMaximum performance controlRapid development, production

Docker Deployment Guide

Janus Gateway Docker Setup

Janus provides an official Docker image. The container requires mounting configuration files and port mappings for both the HTTP API and RTP/RTCP ranges.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
version: "3.8"

services:
  janus:
    image: meetecho/janus-gateway:latest
    container_name: janus-gateway
    restart: unless-stopped
    ports:
      - "8088:8088"    # HTTP API
      - "8089:8089"    # HTTPS API (if configured)
      - "8188:8188"    # Admin/Monitor
      - "10000-10100:10000-10100/udp"  # RTP range
    volumes:
      - ./janus-config:/opt/janus/etc/janus
      - ./janus-plugins:/opt/janus/lib/janus/plugins
    environment:
      - JANUS_CONFIG=/opt/janus/etc/janus/janus.jcfg
    # For NAT traversal, set your external IP:
    # - JANUS_NAT_1_1=<your-public-ip>

The Janus configuration file (janus.jcfg) controls core settings:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
general: {
    interface = 127.0.0.1
    port = 8088
    admin_interface = 127.0.0.1
    admin_port = 7088
    admin_key = "your-admin-secret-key"
    stun_server = "stun.l.google.com"
    stun_port = 19302
    nat_1_1_mapping = "YOUR-PUBLIC-IP"
}

media: {
    rtp_port_range = 10000-10100
    start_threaded_sessions = true
}

For production, configure a TURN server (see our TURN/STUN server guide) to handle clients behind restrictive NATs.

LiveKit Docker Setup

LiveKit is the easiest to deploy with Docker. It requires a configuration file and optionally Redis for multi-node clustering.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
version: "3.8"

services:
  livekit:
    image: livekit/livekit-server:latest
    container_name: livekit-server
    restart: unless-stopped
    ports:
      - "7880:7880"    # HTTP/WebSocket
      - "7881:7881"    # gRPC internal
      - "50000-60000:50000-60000/udp"  # UDP media
      - "50000-60000:50000-60000/tcp"  # TCP media (fallback)
    volumes:
      - ./livekit-config.yaml:/config/livekit.yaml:ro
    command: --config /config/livekit.yaml
    depends_on:
      - redis

  redis:
    image: redis:7-alpine
    container_name: livekit-redis
    restart: unless-stopped
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data

volumes:
  redis-data:

LiveKit configuration (livekit-config.yaml):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
port: 7880
rtc:
  udp_port: 7882
  tcp_port: 7881
  external_ips:
    - YOUR-PUBLIC-IP
redis:
  address: redis:6379
keys:
  api-key-1: "your-api-secret-key-here"
logging:
  level: info
  json: true

For single-node deployments, you can omit the Redis section. LiveKit will operate in standalone mode. For production, Redis enables horizontal scaling — any node can accept connections and route participants to the correct room.

Mediasoup Docker Setup

Since Mediasoup is a library (not a standalone server), there’s no official Docker image. You’ll need to create your own. Here’s a production-ready setup:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
version: "3.8"

services:
  mediasoup-app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: mediasoup-server
    restart: unless-stopped
    ports:
      - "3000:3000"    # Signaling (WebSocket/HTTP)
      - "40000-40100:40000-40100/udp"  # RTP range
      - "40000-40100:40000-40100/tcp"  # TCP fallback
    environment:
      - DOMAIN=your-domain.com
      - MEDIATEMPORT=40000
      - MEDIAMAXPORT=40100
    volumes:
      - ./app:/app
      - ./certs:/app/certs:ro

volumes:
  certs:

Your Dockerfile for the Mediasoup application:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
FROM node:20-alpine

RUN apk add --no-cache python3 make g++

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .

EXPOSE 3000
EXPOSE 40000-40100/udp
EXPOSE 40000-40100/tcp

CMD ["node", "server.js"]

A minimal server.js skeleton:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const mediasoup = require('mediasoup');
const https = require('https');
const fs = require('fs');

// Worker setup — one per CPU core
let worker;
async function createWorker() {
  worker = await mediasoup.createWorker({
    logLevel: 'warn',
    rtcMinPort: 40000,
    rtcMaxPort: 40100,
  });
  worker.on('died', () => process.exit(1));
}

// Router setup
async function createRouter() {
  const mediaCodecs = [
    { kind: 'audio', mimeType: 'audio/opus', clockRate: 48000, channels: 2 },
    { kind: 'video', mimeType: 'video/VP8', clockRate: 90000 },
    { kind: 'video', mimeType: 'video/VP9', clockRate: 90000, parameters: { 'profile-id': 2 } },
    { kind: 'video', mimeType: 'video/H264', clockRate: 90000 },
  ];
  return await worker.createRouter({ mediaCodecs });
}

Mediasoup requires you to implement the signaling layer yourself — WebSocket handling, room management, and participant coordination are all application-level concerns.

Performance and Scalability

Janus Gateway

Janus runs as a single process. Its C implementation is efficient, but it doesn’t have built-in clustering. For horizontal scaling, you need:

  • A load balancer distributing WebSocket connections
  • Shared state (database or Redis) for room participant tracking
  • External TURN server for NAT traversal

Single-node Janus can comfortably handle 200-500 concurrent participants depending on codec and resolution. The VideoRoom plugin is the most commonly used and well-tested path.

Mediasoup

Mediasoup’s architecture is inherently multi-process. Each worker runs on a separate CPU core, and your Node.js application distributes routers across workers. This gives excellent single-node utilization — a 16-core server can run 16 independent workers, each handling hundreds of connections.

Horizontal scaling requires application-level orchestration. Your Node.js code must decide which worker handles which room and communicate this via WebSocket signaling.

LiveKit

LiveKit has the best out-of-the-box scalability. With Redis backing, multiple LiveKit nodes form a cluster automatically:

  • Any node accepts incoming connections
  • Redis tracks which rooms exist on which nodes
  • Clients are routed to the correct node via redirect
  • Automatic failover if a node goes down

A 3-node LiveKit cluster with Redis can handle thousands of concurrent participants. The architecture mirrors what commercial platforms use, giving you enterprise-scale infrastructure with open-source software.

When to Choose Each Platform

Choose Janus Gateway When:

  • You need SIP gateway functionality (bridging WebRTC to traditional phone systems)
  • You want a plugin architecture for custom extensions
  • You’re building something unconventional that doesn’t fit standard room models
  • You prefer C and want to dive into the source code
  • You need MCU (audio/video mixing) capabilities

Choose Mediasoup When:

  • You need maximum control over media routing
  • You’re building a highly customized real-time application
  • You already have a Node.js backend and want to embed WebRTC
  • You need fine-grained resource management per CPU core
  • You want to minimize latency with direct media path control

Choose LiveKit When:

  • You want to ship fast with a complete SDK ecosystem
  • You need horizontal scaling without building it yourself
  • You’re building a standard video conferencing or live streaming app
  • You want built-in recording, streaming, and SIP gateway services
  • You need mobile SDKs (iOS, Android, Flutter, React Native)
  • Your team prefers Go over C or C++

Reverse Proxy Configuration

All three SFU servers need a reverse proxy for TLS termination. Here’s a Caddy configuration that works for LiveKit (adapt paths for Janus or your Mediasoup app):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
webrtc.your-domain.com {
    reverse_proxy localhost:7880

    # WebSocket upgrade is essential for WebRTC signaling
    header_up Connection {>Connection}
    header_up Upgrade {>Upgrade}

    tls {
        protocols tls1.2 tls1.3
    }
}

For Janus behind Caddy:

1
2
3
4
5
6
7
8
9
janus.your-domain.com {
    reverse_proxy localhost:8088

    # Admin endpoint — restrict access
    @admin path /admin/*
    reverse_proxy @admin localhost:8188 {
        # Add IP restriction or auth here
    }
}

Monitoring and Health Checks

Janus Health Check

1
curl -s http://localhost:8188/admin/monitor?key=your-admin-key | python3 -m json.tool

LiveKit Health Check

1
2
curl -s http://localhost:7880/ | python3 -m json.tool
# Returns: {"status":"OK"}

Mediasoup Health Check

Implement in your Node.js application:

1
2
3
4
5
6
7
app.get('/health', (req, res) => {
  res.json({
    status: 'ok',
    workers: worker.getNumRouters(),
    uptime: process.uptime(),
  });
});

FAQ

What is a WebRTC SFU and why do I need one?

A Selective Forwarding Unit (SFU) is a server that receives media streams from participants and selectively forwards them to others. Without an SFU, WebRTC uses peer-to-peer mesh networking, which means every participant sends and receives a stream to/from every other participant. In a 10-person call with mesh, each participant sends 9 streams and receives 9 streams — 90 total streams. An SFU reduces this to 10 sends and 10 receives total, dramatically lowering bandwidth and CPU usage.

Can these SFU servers handle large conferences (100+ participants)?

LiveKit handles this best out of the box with its Redis-backed clustering. A 3+ node cluster can support thousands of participants. Janus and Mediasoup can handle large conferences on a single powerful node, but horizontal scaling requires more manual infrastructure work. For consistently large events, LiveKit’s architecture gives you the easiest path to scale.

Do I need a TURN server alongside the SFU?

Yes, in production you almost certainly need a TURN server. While STUN handles simple NAT traversal, many corporate firewalls and mobile networks block UDP entirely, requiring TCP relay through TURN. Coturn is the standard open-source TURN/STUN server and integrates with all three SFU platforms. See our complete TURN/STUN guide for setup instructions.

Which SFU has the best mobile SDK support?

LiveKit has the most comprehensive SDK ecosystem, with official libraries for JavaScript/TypeScript, Swift (iOS), Kotlin (Android), Flutter, React Native, and Unity. Mediasoup has a JavaScript SDK and community-maintained mobile wrappers. Janus has a JavaScript client and some community mobile SDKs, but they’re less mature. If mobile support is critical, LiveKit is the clear choice.

Can I record WebRTC sessions with these servers?

All three support recording but in different ways. LiveKit has a dedicated Egress service that records rooms to files, streams to RTMP endpoints, or outputs segmented playlists. Janus has a recording plugin that saves audio/video to disk in various formats. Mediasoup requires you to capture output from consumers and encode recordings yourself — more flexible but more work.

How do I handle NAT and firewall traversal?

Each SFU needs to know its public IP address for ICE candidate generation. Configure nat_1_1_mapping in Janus, external_ips in LiveKit, or announce the public IP in your Mediasoup signaling. Additionally, deploy a TURN server (like Coturn) for clients behind symmetric NATs or restrictive corporate firewalls. The TURN server acts as a relay when direct peer-to-SFU connections fail.

Is it worth self-hosting vs. using a commercial WebRTC platform?

If you have more than a few hundred participant-hours per month, self-hosting is usually cheaper. Commercial platforms charge $0.004-$0.01 per participant-minute. At 100 participants in a 1-hour daily meeting, that’s $72-$180/month just for WebRTC. A $20/month VPS running LiveKit handles the same load at a fraction of the cost. The tradeoff is operational responsibility — you manage the infrastructure, updates, and monitoring.

Internal Resources

For related reading, see our complete TURN/STUN server guide for NAT traversal setup, the BigBlueButton vs MiroTalk comparison for full video conferencing platforms, and our VoIP PBX guide for SIP gateway integration.

Advertise here
Advertise here