Fluxer uses LiveKit as its WebRTC Selective Forwarding Unit (SFU) for voice and video calls. This guide covers deployment, configuration, and troubleshooting.
Overview
LiveKit provides:
Voice Channels Multi-participant voice channels with spatial audio, noise suppression, and echo cancellation.
Video Calls HD video with simulcast, screen sharing, and adaptive bitrate.
TURN/STUN NAT traversal with built-in TURN server for users behind restrictive firewalls.
Recording Optional call recording and egress to S3-compatible storage.
Deployment Options
You can deploy LiveKit in several ways:
livekitctl (Recommended)
Docker Compose
Managed LiveKit
Fluxer provides livekitctl, a CLI tool that automates the entire LiveKit stack installation: Installation curl -fsSL https://fluxer.app/get/livekitctl | sudo bash
Bootstrap LiveKit sudo livekitctl bootstrap \
--livekit-domain livekit.chat.example.com \
--turn-domain turn.chat.example.com \
--email [email protected] \
--webhook-url https://chat.example.com/api/webhooks/livekit
This command:
Installs LiveKit, Caddy (with L4 module), coturn, and Redis
Configures DNS verification and Let’s Encrypt TLS
Sets up systemd services
Generates API keys and secrets
Configures firewall rules (if --firewall flag used)
/opt/livekit/
bin/
livekit-server # LiveKit binary
/etc/livekit/
livekitctl-state.json # Bootstrap state
secrets.json # API keys (use in Fluxer config)
livekit.yaml # LiveKit config
caddy.json # Caddy reverse proxy config
coturn.conf # TURN server config
Systemd services:
livekit-kv.service # Redis (localhost:6379)
livekit-coturn.service # TURN server
livekit.service # LiveKit SFU
caddy.service # Reverse proxy
Retrieve Secrets After bootstrapping, get your API credentials: sudo cat /etc/livekit/secrets.json
{
"livekit_api_key" : "APIxxxxxxxxx" ,
"livekit_api_secret" : "secretxxxxxxxxxxxxxxxxx" ,
"livekit_webhook_key" : "webhookxxxxxxxxxxxxx"
}
Use these in your Fluxer configuration. Add LiveKit to your existing compose.yaml: services :
livekit :
image : livekit/livekit-server:v1.9.11
container_name : livekit
profiles : [ 'voice' ]
restart : unless-stopped
command : [ '--config' , '/etc/livekit/livekit.yaml' ]
volumes :
- ./config/livekit.yaml:/etc/livekit/livekit.yaml:ro
ports :
- '7880:7880' # HTTP/WebSocket
- '7881:7881' # RTC TCP
- '3478:3478/udp' # TURN
- '50000-50100:50000-50100/udp' # RTC media
healthcheck :
test : [ 'CMD-SHELL' , 'wget -qO- http://127.0.0.1:7880 || exit 1' ]
interval : 15s
timeout : 5s
retries : 5
Start with the voice profile: docker compose --profile voice up -d
Use LiveKit Cloud for zero-ops deployment:
Sign up at https://cloud.livekit.io/
Create a project and get API credentials
Configure Fluxer to use your LiveKit Cloud URL:
{
"integrations" : {
"voice" : {
"enabled" : true ,
"api_key" : "APIxxxxxxxxx" ,
"api_secret" : "secretxxxxxxxxxxxxxxxxx" ,
"url" : "wss://your-project.livekit.cloud" ,
"webhook_url" : "https://chat.example.com/api/webhooks/livekit"
}
}
}
LiveKit Cloud includes global edge distribution, automatic scaling, and built-in TURN servers.
LiveKit Configuration
Create config/livekit.yaml for self-hosted deployments:
port : 7880
# API Keys (generate with `livekit-server generate-keys`)
keys :
APIxxxxxxxxx : secretxxxxxxxxxxxxxxxxx
rtc :
# Port range for WebRTC connections
port_range_start : 50000
port_range_end : 50100
# TCP fallback port
tcp_port : 7881
# Use external IP for clients (auto-detected if not set)
# use_external_ip: true
# node_ip: 203.0.113.42
turn :
enabled : true
# TURN server for NAT traversal
udp_port : 3478
# External TURN server (optional)
# external_tls: true
room :
# Automatically create rooms when clients connect
auto_create : true
# Maximum participants per room
max_participants : 100
# Timeout before empty rooms are deleted (seconds)
empty_timeout : 300
# Enable room departure timeout
departure_timeout : 20
# Webhook for events (participant joined, track published, etc.)
webhook :
api_key : webhookxxxxxxxxxxxxx
urls :
- https://chat.example.com/api/webhooks/livekit
# Logging
logging :
level : info
# sample: true # Enable to reduce log volume
# Redis for distributed deployments (optional)
# redis:
# address: redis:6379
# db: 0
# Recording (optional)
# egress:
# s3:
# access_key: your-s3-key
# secret: your-s3-secret
# endpoint: s3.amazonaws.com
# bucket: livekit-recordings
Generate API Keys
If not using livekitctl, generate keys manually:
docker run --rm livekit/livekit-server:v1.9.11 generate-keys
Output:
API Key: APIxxxxxxxxx
API Secret: secretxxxxxxxxxxxxxxxxx
Fluxer Configuration
Update your config/config.json to enable voice:
{
"integrations" : {
"voice" : {
"enabled" : true ,
// API credentials from livekit.yaml
"api_key" : "APIxxxxxxxxx" ,
"api_secret" : "secretxxxxxxxxxxxxxxxxx" ,
// WebSocket URL for clients (wss:// in production)
"url" : "wss://chat.example.com/livekit" ,
// Webhook endpoint for LiveKit events
"webhook_url" : "https://chat.example.com/api/webhooks/livekit" ,
// Default voice region (optional)
"default_region" : {
"id" : "default" ,
"name" : "Default Region" ,
"emoji" : "🌐" ,
"latitude" : 40.7128 ,
"longitude" : -74.0060
}
}
}
}
LiveKit WebSocket URL that clients connect to. Must be publicly accessible.
Self-hosted : wss://livekit.example.com or wss://chat.example.com/livekit
LiveKit Cloud : wss://your-project.livekit.cloud
Development : ws://localhost:7880 (HTTP OK for localhost)
Endpoint where LiveKit sends event webhooks. Used to sync room state and participant changes.
If specified, Fluxer automatically creates this voice region on startup if none exist. Useful for single-region deployments.
Network Configuration
Required Ports
LiveKit requires these ports to be accessible from the internet:
Port Protocol Purpose Firewall Rule 7880 TCP HTTP/WebSocket (internal) Not exposed (behind reverse proxy)7881 TCP RTC over TCP (fallback) ufw allow 7881/tcp3478 UDP TURN/STUN ufw allow 3478/udp50000-50100 UDP RTP/RTCP media streams ufw allow 50000:50100/udp
Critical: UDP ports cannot be proxied through Cloudflare Tunnels, ngrok, or similar HTTP-only tunnels. They must be directly accessible via the server’s public IP.
Firewall Configuration
UFW (Ubuntu/Debian)
firewalld (RHEL/CentOS)
iptables
Cloud Providers
# Allow HTTPS (for WebSocket signaling via reverse proxy)
sudo ufw allow 443/tcp
# Allow LiveKit UDP ports
sudo ufw allow 7881/tcp
sudo ufw allow 3478/udp
sudo ufw allow 50000:50100/udp
sudo ufw enable
sudo firewall-cmd --permanent --add-port=443/tcp
sudo firewall-cmd --permanent --add-port=7881/tcp
sudo firewall-cmd --permanent --add-port=3478/udp
sudo firewall-cmd --permanent --add-port=50000-50100/udp
sudo firewall-cmd --reload
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -p tcp --dport 7881 -j ACCEPT
iptables -A INPUT -p udp --dport 3478 -j ACCEPT
iptables -A INPUT -p udp --dport 50000:50100 -j ACCEPT
# Save rules
iptables-save > /etc/iptables/rules.v4
AWS Security Groups: Type Protocol Port Range Source
HTTPS TCP 443 0.0.0.0/0
Custom TCP TCP 7881 0.0.0.0/0
Custom UDP UDP 3478 0.0.0.0/0
Custom UDP UDP 50000-50100 0.0.0.0/0
GCP Firewall Rules: gcloud compute firewall-rules create livekit-rtc \
--allow tcp:7881,udp:3478,udp:50000-50100 \
--source-ranges 0.0.0.0/0
Reverse Proxy Configuration
Proxy the LiveKit WebSocket endpoint through your existing reverse proxy:
/etc/nginx/sites-available/fluxer
server {
listen 443 ssl http2;
server_name chat.example.com;
# ... existing Fluxer config ...
# LiveKit WebSocket proxy
location /livekit {
proxy_pass http://127.0.0.1:7880;
proxy_http_version 1.1 ;
proxy_set_header Upgrade $ http_upgrade ;
proxy_set_header Connection "upgrade" ;
proxy_set_header Host $ host ;
proxy_set_header X-Real-IP $ remote_addr ;
proxy_set_header X-Forwarded-For $ proxy_add_x_forwarded_for ;
proxy_set_header X-Forwarded-Proto $ scheme ;
# Disable buffering for WebSocket
proxy_buffering off ;
# Increase timeouts
proxy_read_timeout 3600 ;
proxy_send_timeout 3600 ;
}
}
chat.example.com {
# Fluxer main app
reverse_proxy localhost:8080
# LiveKit WebSocket
handle /livekit* {
reverse_proxy localhost:7880
}
}
livekit :
image : livekit/livekit-server:v1.9.11
labels :
- "traefik.enable=true"
- "traefik.http.routers.livekit.rule=Host(`chat.example.com`) && PathPrefix(`/livekit`)"
- "traefik.http.routers.livekit.tls.certresolver=letsencrypt"
- "traefik.http.services.livekit.loadbalancer.server.port=7880"
DNS Configuration
If using a dedicated subdomain for LiveKit:
A livekit.chat.example.com → 203.0.113.42
AAAA livekit.chat.example.com → 2001:db8::1
Then configure LiveKit to advertise this domain:
rtc :
use_external_ip : false # Use domain instead of IP
# Clients will connect to livekit.chat.example.com
Multi-Region Voice
For global deployments, deploy multiple LiveKit servers in different regions.
1. Deploy Regional LiveKit Servers
Deploy LiveKit in each region (e.g., us-east, eu-west, ap-south):
# Region: US East (Virginia)
sudo livekitctl bootstrap \
--livekit-domain livekit-us.chat.example.com \
--turn-domain turn-us.chat.example.com \
--email [email protected]
# Region: EU West (Ireland)
sudo livekitctl bootstrap \
--livekit-domain livekit-eu.chat.example.com \
--turn-domain turn-eu.chat.example.com \
--email [email protected]
2. Add Regions in Fluxer Admin Panel
Navigate to Admin Panel → Voice → Regions and add:
[
{
"id" : "us-east" ,
"name" : "US East" ,
"emoji" : "🇺🇸" ,
"latitude" : 37.7749 ,
"longitude" : -122.4194 ,
"servers" : [
{
"url" : "wss://livekit-us.chat.example.com" ,
"api_key" : "APIxxxxxxxxx" ,
"api_secret" : "secretxxxxxxxxxxxxxxxxx"
}
]
},
{
"id" : "eu-west" ,
"name" : "EU West" ,
"emoji" : "🇪🇺" ,
"latitude" : 53.3498 ,
"longitude" : -6.2603 ,
"servers" : [
{
"url" : "wss://livekit-eu.chat.example.com" ,
"api_key" : "APIxxxxxxxxx" ,
"api_secret" : "secretxxxxxxxxxxxxxxxxx"
}
]
}
]
Fluxer automatically selects the closest region based on user latency.
Monitoring
LiveKit Logs
sudo livekitctl logs --service livekit.service --lines 200
# Follow logs in real-time
sudo journalctl -u livekit.service -f
docker compose logs -f livekit
Health Checks
# LiveKit health endpoint
curl http://localhost:7880/
# Check if ports are listening
ss -tuln | grep -E '7880|7881|3478|50000'
# Test UDP connectivity (from client)
nc -u -v -w 1 chat.example.com 3478
Metrics
LiveKit exposes Prometheus metrics at http://localhost:7880/metrics:
scrape_configs :
- job_name : 'livekit'
static_configs :
- targets : [ 'localhost:7880' ]
Key metrics:
livekit_room_total - Active rooms
livekit_participant_total - Connected participants
livekit_track_published_total - Published audio/video tracks
livekit_packet_total - RTP packets sent/received
Troubleshooting
Voice channels not appearing in Fluxer
Verify voice is enabled in config:
grep -A 5 '"voice"' config/config.json
Check Fluxer logs for LiveKit connection errors:
docker compose logs fluxer_server | grep -i livekit
Ensure api_key and api_secret match between livekit.yaml and Fluxer config.
Cannot connect to voice channel
Symptoms: “Connecting…” spinner never completes
Check if LiveKit is accessible:
curl -I https://chat.example.com/livekit
# Should return 404 or 426 (Upgrade Required)
Verify WebSocket URL in config matches your reverse proxy path.
Check browser console for WebSocket errors (F12 → Console).
Test with LiveKit’s CLI:
livekit-cli join-room \
--url wss://chat.example.com/livekit \
--api-key APIxxxxxxxxx \
--api-secret secretxxxxxxxxxxxxxxxxx \
--room test-room \
--identity test-user
No audio/video after connecting
Cause: UDP ports blocked or TURN not working
Verify UDP ports are open:
sudo ufw status | grep -E '3478|7881|50000'
Test TURN server:
# Install trickle-ice
npm install -g trickle-ice
# Test TURN connectivity
trickle-ice chat.example.com 3478
Check LiveKit logs for ICE connection failures:
sudo journalctl -u livekit.service | grep -i 'ice\|turn'
Ensure TURN is enabled in livekit.yaml:
turn :
enabled : true
udp_port : 3478
Enable echo cancellation in LiveKit:
audio :
echo_cancellation : true
noise_suppression : true
auto_gain_control : true
Ask users to use headphones or reduce speaker volume.
Check if multiple clients are running on the same device.
High CPU usage on LiveKit server
Check number of concurrent rooms/participants:
curl -s http://localhost:7880/metrics | grep livekit_room_total
Consider scaling horizontally (add more LiveKit nodes).
Reduce video quality in client settings:
Lower resolution (720p → 480p)
Reduce framerate (30fps → 15fps)
Disable simulcast for low-end clients
Advanced Configuration
Custom Codecs
audio :
# Prefer Opus codec
codecs :
- opus
video :
# Use VP8 (better compatibility) or VP9 (better compression)
codecs :
- vp8
- h264
Recording to S3
egress :
s3 :
access_key : your-s3-access-key
secret : your-s3-secret-key
endpoint : s3.amazonaws.com
bucket : livekit-recordings
region : us-east-1
Trigger recording via API:
curl -X POST https://chat.example.com/api/v1/channels/:id/record \
-H "Authorization: Bearer your-token"
Redis for Distributed LiveKit
For multi-node LiveKit deployments, use Redis for state synchronization:
redis :
address : redis:6379
db : 0
username : default
password : your-redis-password
All LiveKit nodes must share the same Redis instance.
Next Steps
Scaling Guide Scale voice infrastructure globally with multi-region deployments
Architecture Understand how LiveKit integrates with Fluxer