-
Notifications
You must be signed in to change notification settings - Fork 82
Description
Hello,
I'm in the process of scaling the Nextcloud Talk HPB.
Currently, I have signalling and NATS (Docker) on one server, three Janus nodes connected to the signal server, and two Coturn nodes connected to the signal server.
I have made a few configuration changes to the signal server and the Janus node, and I would like to check that they are correct and that there are no Nextcloud Talk-related dependencies.
1. Janus Node
- nginx reverse proxy in front for TLS termination -> proxy_pass to the signaling proxy that listen on 127.0.0.1: 9090
- signaling proxy with multistream enabled
- signaling proxy "talk" to the janus server ws://localhost:8188
[http]
# Keep the proxy listener bound to localhost; Nginx does SSL termination.
listen = 127.0.0.1:9090
[app]
# Location for geo-based publisher placement.
country = DE
# Hostname of this proxy; must be publicly resolvable and match TLS cert.
hostname = nc-janus01.domain.de
# Proxies to trust for X-Forwarded-For/X-Real-IP.
# Only localhost unless Nginx runs remotely.
trustedproxies = 127.0.0.1
# Token type: static mapping of ID -> public key
tokentype = static
# Token for inter-proxy Remote Streams (shared across all proxies)
token_id = cluster
token_key = /etc/signaling/cluster.key # private key for this proxy
# Uncomment if using a private CA during testing (not recommended in prod)
# skipverify = true
[bandwidth]
# Total allowed publisher ingress and subscriber egress in Mbit/s.
# 8000 ≈ 8 Gbit/s target; keeps headroom for 10 GbE NIC.
incoming = 8000
outgoing = 8000
[tokens]
# Public key for signaling server(s) allowed to connect
server1 = /etc/signaling/server1.pub
# Public key for inter-proxy Remote Streams (same file on all proxies)
cluster = /etc/signaling/cluster.pub
[mcu]
# Type of MCU (Janus)
type = janus
# Janus WebSocket control URL (local)
url = ws://localhost:8188/
# Per-stream bitrates (in bits/sec)
maxstreambitrate = 1200000 # ~1.2 Mbps per camera
maxscreenbitrate = 2097152 # ~2.0 Mbps per screenshare
# (Optional) Restrict ICE candidates to these subnets
# allowedcandidates = 10.0.0.0/8,2a12:79c0::/48
# (Optional) Block certain candidates from being advertised
# blockedcandidates = 1.2.3.0/24
[stats]
# Allow local and monitoring network to query /stats
allowed_ips = 127.0.0.1,10.0.0.0/8
In the Janus configuration file (janus.cfg), I have the following configuration in the 'media' block:
media: {
# UDP port range for SRTP media; open this in firewall (IPv4 + IPv6)
rtp_port_range = "20000-40000"
# Enable IPv6 if host has a public v6 address
ipv6 = true
ipv6_linklocal = false # usually false; link-local only works on same L2 segment
# Keep packets under common MTUs
dtls_mtu = 1200
# Clean up dead PeerConnections
no_media_timer = 12
# Mark a peer as slow after sustained ~10% packet loss
slowlink_threshold = 10
# TWCC feedback every 100 ms for responsive congestion control
twcc_period = 100
# Drop unnecessary retransmits right after keyframes
nack_optimizations = true
# NACK queue minimum; 500 is good for HD-level recovery window
min_nack_queue = 500
}
This configuration is in the NAT block.
nat: {
# stun_server = ""
# stun_port = 0
nice_debug = false
full_trickle = true
#ice_nomination = "regular"
#ice_keepalive_conncheck = true
ice_lite = true
#ice_tcp = true`
#turn_rest_api_key = ""
Unlike most HPB setup guides for single-server deployments, my scaled setup has no STUN server configured in janus.jcfg and is in ICE Lite mode.
This is because, in the HPB architecture, the signalling server determines and provides all ICE candidates (including the public-facing IPs). Janus does not need to perform STUN lookups itself, removing an extra network dependency and simplifying firewall rules.
ICE Lite is enabled because, in this setup, Janus is acting as the 'always reachable' ICE peer: it has a stable public IP address (advertised via the signalling server) and does not need to perform full ICE connectivity checks like a browser does. This reduces the complexity of setting up connections and the CPU load on the Janus side, which is helpful when scaling out with multiple Janus nodes.
I have also commented out turn_rest_api_key because the HPB signalling server handles TURN credentials entirely in this architecture. Janus never needs to request TURN credentials directly, so the TURN REST API integration in Janus is unused. This avoids redundant configuration and ensures that TURN settings are centralised in the signalling server.
Is this right or do I have a fallacy?
2. Signal Server (with NATS)
- Nginx reverse proxy with TLS termination and ProxyPass to the Signal Server
- NATS in a Docker container
First of all, I'm wondering why NATS is required when only the one signaling server is running and no information needs to be exchanged between different signal servers. Are there internal exchanges where NATS is needed?
In any case, no video comes up without NATS.
In the server.conf I have this configuration:
[http]
listen = 127.0.0.1:8080
[app]
debug = false
[sessions]
hashkey = <>
blockkey = <>
[clients]
internalsecret = <>
[backend]
backends = nextcloud
allowall = false
secret = <>
timeout = 10
sessionlimit = 100
connectionsperhost = 32
maxstreambitrate = 1200000
maxscreenbitrate = 2097152
[nextcloud]
url = https://cloud1.de
[nats]
url = nats://localhost:4222
[mcu]
type = proxy
url = https://janus01.domain.de https://janus02.domain.de https://janus03.domain.de
token_id = server1
token_key = /etc/signaling/server1.key
maxstreambitrate = 1200000
maxscreenbitrate = 2097152
[turn]
apikey = <>
secret = <>
servers = turn:turn01.domain.de:443?transport=udp,turn:turn01.domain.de:443?transport=tcp,turn:turn02.domain.de:443?transport=udp,turn:turn02.domain.de:443?transport=tcp
[geoip]
[geoip-overrides]
[stats]
However, if I comment out the API key (the so-called Janus API key) in the [turn] section of server.conf, the signalling server fails to start.
In my architecture, this key is not required logically, since Janus nodes do not request TURN credentials from the signalling server. Nevertheless, the code path for startup still enforces the presence of an API key.
This seems to be hard-coded validation rather than a functional necessity and could be made optional for scaled deployments where Janus is ICE-lite and TURN lookups are client-side only.
3. coturn server
Nothing special on this config, but for the complet setup:
tls-listening-port=443
listening-ip=<>
relay-ip=<>
listening-ip=<>
relay-ip=<>
min-port=20000
max-port=65535
verbose
fingerprint
lt-cred-mech
use-auth-secret
static-auth-secret=<>
realm=turn01.domain.de
cert=/etc/acme_ssl/rsa-certs/fullchain.pem
pkey=/etc/acme_ssl/rsa-certs/privkey.pem
cipher-list="ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AE>
dh-file=/etc/turnserver/ssl/dhp.pem
# syslog
keep-address-family
no-cli
no-tlsv1
no-tlsv1_1
# no-loopback-peers
no-multicast-peers
Could someone please confirm whether, in a scaled HPB deployment for Nextcloud Talk with ICE-Lite Janus nodes and signalling-provided ICE candidates, it is correct to omit the 'stun_server' and 'turn_rest_api_key' entries in 'janus.jcfg'? Are there any Nextcloud Talk dependencies that would require them?