Complete Guide on How to Setup a Nextcloud Spreed Signaling Server (i.e. the Talk High-Performance Backend) on Debian (11) Bullseye

This is a tutorial on how to setup the High-performance backend for the Talk/Spreed app within a Nextcloud instance. While I found several tutorials online that guide through parts of the process, those I found were insufficient for allowing access to users behind strict firewalls or NATs, and additionally were mostly written for Ubuntu. This tutorial aims at creating a fully functional High-Performance backend installed on Debian Bullseye, and which also clients from within big organisations or companies can access.

This tutorial will explain how to prepare and set up a server to use the standalone spreed signaling server, plus, how to combine it with a TURN server. I have been hosting video conferences and workshops with for an academic audience, where the users often sit in universities or big organisation, and their internet traffic is limited by NAT and firewall settings. This is, because in big organisation sometimes only the most necessary ports for webbrowsing (53, 80, 443) are open, and the default ports of turn and signaling servers are outside that range. So in order to allow their access, both the TURN server and the signaling server need to be accessible on port 443. You can achieve that by either running the TURN server on a dedicated VPS, or by adding it to an existing server (such as the signaling server you are going to set up) while using an additional IPv4 address.

This tutorial will show both options, but most importantly, it will provide an inclusive setup to also allow access from clients behind strictly set firewalls.



  • You will need a server (VPS) with Debian 11 minimal pre-installed for the signaling server.   
    (Nextcloud suggests to use a server with dedicated cores; For my purposes [<30 video conference participants], a small VPS with 2vCores and 2GB RAM has been sufficient so far)
  • A Domain and access to the zone file (DNS settings)
  • For the TURN server you will either need a second VPS or an additional IPv4 address that you can bind to an existing server (see chpt. 2 for more details), such as the signaling server you are going to set up.


  • You know how to edit your DNS settings
  • You know how to install a VPS
  • You know how to open a Linux terminal

Text between the <less/more marks> indicate a variable you will create or choose (such as a user name or secret), but please leave out the marks themselves when entering the values into the configuration files.


1. Prepare Server

Let’s start with creating the DNS entries for your signaling and TURN servers. Go to your domain’s DNS settings and create two A entries, like

signaling A <XXX.XXX.XXX.XXX>

for your signaling server, and one for your TURN server like


(Let’s do that at the beginning, so when we get to creating certificates, the subdomains should already be accessible). You may want to change the TTL to a low value, such as 600.


After renting your server, you should have gotten some root login credentials. Open the terminal on your local machine and SSH into your new server using those credentials:

ssh root@XXX.XXX.XXX.XXX

(Optionally) you may want to rename the hostname of your VPS:

hostnamectl set-hostname signaling.your.domain


First important step: Change your root password with


(If you want a random password, you can use pwgen; If you do, please write it down somewhere :-)


1.a) Create a SSH User

Create a new user

apt install sudo

useradd -m <ssh-user>

passwd <ssh-user>

Add the user to the sudo group

usermod -a -G sudo <ssh-user>

And prepare for ssh usage

su <sshuser>



Lets make some changes to the ssh server to use a different than the default port, , to disallow root login, limit ssh access to IPv4, and to allow password-less authentication at ssh login:

sudo nano /etc/ssh/sshd_config

Uncomment (i.e. take out the "#" in front of a line), modify, or add the lines accordingly

Port 2222
PermitRootLogin no
AddressFamily inet
PubkeyAuthentication yes

and restart the ssh server

sudo systemctl restart sshd

Now you can log out, or open a second ssh session on your desktop/local computer, pass on your ssh-key and, login again.


Create a ssh key

ssh-keygen (press "enter" when asked for a password to leave it empty)

and copy it to the VPS:

ssh-copy-id -i ~/.ssh/ -p 2222 <ssh-user>@signaling.your.domain   


After that you can login without a password to your VPS

ssh -p 2222 <ssh-user>@signaling.your.domain

Go back into the ssh server configuration

sudo nano /etc/ssh/sshd_config

so you can disable password authentication for ssh, and only allow login for your <ssh-user>

PasswordAuthentication no
AllowUsers <ssh-user> 

Restart the ssh server

sudo systemctl restart sshd

While you could continue using sudo to make further changes, we will switch back to su for convenience

sudo -s


1.b) Firewalling and Brute Force Mitigation

Let's further enhance the VPS security by adding basic firewall and brute force mitigation

apt install ufw fail2ban -y

Configure the firewall to allow only incoming traffic via IPv4 to your ssh server

ufw allow proto tcp to port 2222

and open port 443 for apache (and the TURN server)

ufw allow 443

Enable the firewall

ufw enable

Increase brute force attack prevention, by creating a new file (for local settings)

nano /etc/fail2ban/jail.local

and add

enable = true 
port = 2222 
logpath = %(sshd_log)s 
backend = %(sshd_backend)s
enable = true 
port = 2222
logpath = %(sshd_log)s 
backend = %(sshd_backend)s

and restart fail2ban

systemctl restart fail2ban   

2. TURN server

According to the Nextcloud doumentation

A TURN server running on port 443 (or 80) is required in almost all scenarios

Even if the Nextcloud Talk High Performance Backend is used and publicly accessible, a TURN server might be needed:

The High Performance Backend uses a certain range of ports for WebRTC media connections (20000-40000 by default). A client could be behind a restrictive firewall that only allows connections to port 443, so even if the High Performance Backend is publicly accessible the client would need to connect to a TURN server in port 443, and the TURN server will then relay the packets to the 20000-40000 range in the High Performance Backend.

For maximum compatibility the TURN server should be configured to listen on port 443. Therefore, when both a TURN server and the High Performance Backend are used each one should run in its own server, or in the same server but each one with its own IP address, as the High Performance Backend will need to bind to port 443 too.

Hence, we can either run a dedicated VPS for the TURN server, or add a second IP to an existing and running VPS, such as the signaling server you are setting up. In the past, I used a dedicated VPS for this purpose, but my server load was too little to justify that (I had sessions with up to 30 participants at once, and the turnserver hardly used any memory or cpu).




2.a) Add additional IP address (Optional)

If you decided to use an additional IPv4, you will most likely have to add it to the network interfaces of the machine you want to add it, in the control panel of your VPS host.

To utilise an additional IPv4 address on an existing server, edit your existing config file or add a new one

nano /etc/network/interfaces.d/<your-config-file>.cfg

and add a new network interface

auto eth0:1
iface eth0:1 inet static
	address YYY.YYY.YYY.YYY


If you use an existing VPS that already has apache installed, we can make the following modifications to apache; if not, then install apache now (which we will need later anyway) 

apt install apache

Make Apache to listen only to original IP address by editing

nano /etc/apache2/ports.conf

and add your original IP address (from the network interface eth0 in your /etc/network/interfaces.d/<your-config-file>.cfg or by looking it up using “hostname -I") in front of the ports apache is listening to

	Listen XXX.XXX.XXX.XXX:80
<IfModule ssl_module>
	Listen XXX.XXX.XXX.XXX:443
<IfModule mod_gnutls.c>
	Listen XXX.XXX.XXX.XXX:443


Reboot server


Potentially you may have to shut down and restart the VPS from the control panel of your VPS host to enable the additional IPv4 address.


Login again

ssh -p 2222 <ssh-user>@signaling.your.domain


2.b) Install and configure coTURN

For installing the TURN server (we will use coTURN) as well as for other software later on, we add the bullseye-backport repository to get later software than the default one. For this purpose, open your apt configuration

nano /etc/apt/sources.list

and add the backports repo to your apt settings:

deb bullseye-backports main 
deb-src bullseye-backports main 

Update and install coTURN

apt update && apt install -t bullseye-backports coturn -y

The coTURN service is executed as an unprivileged user like turnserver. Due to this by default coTURN can not use privileged ports, like port 443. Linux kernel capabilities could be used to overcome this limitation. Capabilities can be associated with executable files using setcap, so you could allow the /usr/bin/turnserver executable to bind sockets to privileged ports with:

setcap cap_net_bind_service=+ep /usr/bin/turnserver


Back up the original configuration file

cp /etc/turnserver.conf /etc/turnserver.conf_ORIG

Create a "static-auth-secret" for you turnserver with

openssl rand -hex 32

and add it along with the other required configuration to

nano /etc/turnserver.conf


If you need more log verbosity when testing your TURN server, uncomment the "#verbose" flag.

Restart your turn server

systemctl restart coturn


2.c) SSL Certificates with Letsencrypt

In order to use TLS encrypted data transfer, install certbot

apt-get install certbot

and create lLtsencrypt certificates

certbot certonly --standalone --preferred-challenges http --deploy-hook "systemctl restart coturn" -d turn.your.domain

Because coTURN doesn't have privileged rights, it is not allowed to read the certificates. Hence, create new folder where to copy the certificates to

sudo mkdir -p /etc/coturn/certs

Change the ownership and access rights for the coTURN user & group

sudo chown -R turnserver:turnserver /etc/coturn/

sudo chmod -R 700 /etc/coturn/

Because Letsecnrypt certificates are only valid for 3 months, they regularly need to be renewed. So you it is wise to automatise the certificate copy process. Create a script that can do that and place it in the renewal-hooks directory of certbot

nano /etc/letsencrypt/renewal-hooks/deploy/

set -e
for domain in $RENEWED_DOMAINS; do
case $domain in
# Make sure the certificate and private key files are
# never world readable, even just for an instant while
# we're copying them into daemon_cert_root.
umask 077
cp "$RENEWED_LINEAGE/fullchain.pem" "$daemon_cert_root/$domain.cert"
cp "$RENEWED_LINEAGE/privkey.pem" "$daemon_cert_root/$domain.key"
# Apply the proper file ownership and permissions for
# the daemon to read its certificate and key.
chown turnserver "$daemon_cert_root/$domain.cert" \
chmod 400 "$daemon_cert_root/$domain.cert" \
service coturn restart >/dev/null

While the simple log configuration of coTURN will overwrite the log file on every start-up, if your server is up and running for a while, we can configure the log to auto-rotate, by creating anew file

nano /etc/logrotate.d/coturn

and add

/var/log/coturn.log {
rotate 7


 3. Install WebRTC Gateway: Janus 

A Janus server (from can be used to act as a WebRTC gateway. To install, use

apt install -t bullseye-backports janus -y

create a turn-rest-api key

openssl rand -base64 16

and change the Janus configuration in

nano /etc/janus/janus.jcfg

Look for the section nat to add your turn-rest-api key and enable "full_trickle"

turn_rest_api_key = <turn-rest-api-key>
full_trickle = true



nano /etc/janus/janus.transport.http.jcfg

and in the section [general] set interface = "lo" 


nano /etc/janus/janus.transport.websockets.jcfg 

and in the section [general] set ws_interface = "lo"


Restart Janus service

systemctl restart janus

It should automagically be enabled on boot, but just to make sure

systemctl enable janus



4. Install and Setup NATS Server

There is a handy docker version available for NATS, which you can take use of.

Install docker

apt install curl -y

curl -sSL | CHANNEL=stable sh

Enable and start docker

systemctl enable docker.service && systemctl start docker.service   

And run NATS in a docker container

docker run --restart=always --name='natsserver' -d -p 4222:4222 -ti nats:latest   



5.Setup Nextcloud Spreed Signaling Server

Now all the components are in place for your Nextcloud signaling server, so let's get to this puzzle piece. First install the dependencies

apt install -t bullseye-backports git automake golang build-essential python3 protobuf-compiler -y

(protobuf-compiler is required since Talk15/NC25; golang need to be from backports, as the version from the default repo is too old for building the signaling server)

Download the signaling server from github and build it

cd /opt 

git clone

cd nextcloud-spreed-signaling/ 

make build


5.a) Running the Signaling Server as Daemon

To run the signaling server as a daemon, copy the binary to Debian's default binary directory

cp bin/signaling /usr/bin/

and create a new user that will run the signaling server

useradd --system --shell /usr/sbin/nologin --comment "Standalone signaling server for Nextcloud Talk." signaling

Create a configuration file for the signaling server with appropriate ownership settings

mkdir /etc/signaling/

touch /etc/signaling/server.conf

chown signaling: /etc/signaling/server.conf

chmod 600 /etc/signaling/server.conf

and copy the shipped sample file for the daemon to Debian's systemd directory

cp dist/init/systemd/signaling.service /etc/systemd/system/signaling.service

Make sure that the signaling server only starts up after Janus, by editing

nano /etc/systemd/system/signaling.service

so that the section [unit] looks like this

Description=Nextcloud Talk signaling server 


Reload the daemon and enable the signaling server daemon

systemctl daemon-reload && systemctl enable signaling


5.b) Configure the Signaling Server

 For the signaling server configuration we will need a couple of secrets, so first create those   

Nextcloud Secret Key

openssl rand -hex 16


openssl rand -hex 16


openssl rand -hex 16


Now we have five keys in total (incl. those from chapter 2 and 3)

Turn-Key from chpt. 2: <your-turnserver-secret-created-above>   
api-key from chpt. 3: <turn-rest-api-key>   
Nextcloud-Secret-Key: <nextcloud-secret-key>   
Block-Key: <block-key>   
Hash-Key: <hash-key>   

Open the signaling server configuration file

nano /etc/signaling/server.conf

and copy, paste and adjust the file (change keys to yours)

listen =

debug = false
[sessions] hashkey = <hash-key> blockkey = <block-key> #You may define several nextcloud instances for which you want to use the signaling server, here we only add one [backend] backends = backend-1 allowall = false timeout = 10 connectionsperhost = 8 #add your nextcloud instance [backend-1] url = https://your.nextcloud.domain secret = <nextcloud-secret-key> [nats] url = nats://localhost:4222
[mcu] type = janus url = ws://
[turn] apikey = <turn-rest-api-key> secret = <your-turnserver-secret-created-in-chapter-2> servers = turn:turn.your.domain:443?transport=tcp


Start the signaling server

systemctl start signaling

and check status of the service:

systemctl status signaling


5.c) Reverse Proxy Server

As you can see from section [http] in the server config, the server listens to on port 8080, so we (install and) configure a webserver and use it to proxy to the signaling server. You can use Nginx, Caddy, or some other server, personally I prefer Apache. If you haven't installed apache already (from chapter 2), you can do it now with

apt install apache2 -y

Create a virtual host by creating the file

nano /etc/apache2/sites-available/signaling.conf

and add

<IfModule mod_ssl.c>
<VirtualHost _default_:443>                ServerAdmin admin@localhost                ServerName localhost                ServerAlias signaling.your.domain        SSLEngine on # Enable http2        Protocols h2 http/1.1 # Use HSTS        Header always set Strict-Transport-Security "max-age=63072000; preload"        <FilesMatch "\.(cgi|shtml|phtml|php)$">                SSLOptions +StdEnvVars        </FilesMatch>
       <Directory /usr/lib/cgi-bin>                SSLOptions +StdEnvVars        </Directory>
ProxyPass   "/standalone-signaling/""ws://"      RewriteEngine On        # Websocket connections from the clients.        RewriteRule ^/standalone-signaling/spreed$ - [L]
       # Backend connections from Nextcloud.        RewriteRule ^/standalone-signaling/api/(.*)$1 [L,P]
       Include /etc/letsencrypt/options-ssl-apache.conf        SSLCertificateFile      /etc/ssl/certs/ssl-cert-snakeoil.pem        SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
       ErrorLog ${APACHE_LOG_DIR}/error.log        CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> </IfModule>

(NOTE: settings http2 and HSTS are not necessarily required for the signaling server, but you may add it to enhance speed and transport security)


Enable the virtual host

a2ensite signaling.conf

as well as the required apache modules

a2enmod proxy proxy_http rewrite proxy_wstunnel headers http2

and restart apache

systemctl restart apache2


5.d) SSL/TLS Certificates

If you haven't already installed certbot from in chapter 2, then do it now; Certainly you will want to add the apache plugin for certbot:

apt install certbot python3-certbot-apache -y   

Run certbot


Select your new virtual host and let certbot install your Letsencrypt certificates.


Make sure that  certbot will run once a week to check in time before your certs expire, and to get new ones. Run

crontab -e

And add

 23 2 * * 1 certbot renew --quiet

at the bottom of the file.



6.Add Signaling and TURN Server to Nextcloud

Open your Nextcloud instance, login as admin, and go to the admin settings (https://your.nextcloud.domain/settings/admin/talk).

In section STUN servers, add

stun:  turn.your.domain:443

In section Turn servers, add

turn and turns turn.your.domain:443  <your-turnserver-secret-created-in-chapter-2>  UDP and TCP

In section High-performance backend   
https://signaling.your.domain  check "Validate SSL certificate" and in the field shared secret add <nextcloud-secret-key>



7. Problems? Check your Logs

If you don't see a green check behind the Turn and high-performance backend settings, check your settings.

  • Make sure you used the created secrets in the right place
  • Check your firewall settings
  • check if all services are up and running
    • systemctl status coturn
    • systemctl status signaling
    • systemctl status janus
    • systemctl status apache2
    • docker ps
  • Check your logs
    • tail -f /var/log/coturn.log
    • tail -f /var/log/syslog
    • tail -f /var/log/janus.log
    • tail -f /var/log/apache2/error.log
    • tail -f /var/log/apache2/access.log
    • docker logs <docker-id>



!!! Happy Video Conferencing !!!



8. Sources:

I didn't come up with all of this. There are a couple of good tutorials that cover at least parts of the process, and I want to thank the authors as of those for sharing their knowledge! Yet, none of the tutorials I found covered my use case, so I compiled my own tutorial for easy reproduction. The resulting tutorial is quite compressed and doesn't provide all the details on the settings. If you want to know more about the background, you may want to check the sources I used for compiling this tutorial:



Add new comment