---
gitea: none
include_toc: true
---

# TURN server

You need a TURN server to connect participants that are behind a NAT firewall.
Because IPv6 doesn't really need TURN, and Chrome can get confused if it has
to use TURN over IPv6, we'll stick to a strict IPv4-only configuration.

Also, because VoIP traffic is only UDP, we won't do TCP.

TURN-functionality can be offered by coturn and LiveKit alike: coturn is used
for legacy calls (only one-on-one, supported in Element Android), whereas
Element Call (supported by ElementX, Desktop and Web) uses LiveKit.

In our documentation we'll enable both, which is probably not the optimal
solution, but at least it results in a system that supports old and new
clients.

Here we'll describe coturn, the dedicated ICE/STUN/TURN server that needs to
be configured in Synapse, [LiveKit](../element-call#livekit) has its own page.

# Installation

Installation is short:

```
apt install coturn
```

For sake of maintainability we'll move the only configuration file into its
own directoy:

```
mkdir /etc/coturn
mv /etc/turnserver.conf /etc/coturn
```

We need to tell systemd to start it with the configuration file on the new
place. Edit the service file with:

```
systemctl edit coturn
```

Contrary to what the comment suggests, only the parts you add will override
the content that's already there. We have to "clean" the `ExecStart` first,
before we assign a new line to it, so this is the bit we add:

```
[Service]
ExecStart=
ExecStart=/usr/bin/turnserver -c /etc/coturn/turnserver.conf --pidfile=/etc/coturn/run/turnserver.pid
```

Create the directory `/etc/coturn/run` and chgrp it to `turnserver`, so that
coturn can write its pid there: `/run/turnserver.pid` can't be written because
coturn doesn't run as root.

This prepares us for the next step: configuring the whole thing.


# DNS and certificate {#dnscert}

As stated before, we only use IPv4, so a CNAME to our machine that also does
IPv6 is a bad idea. Fix a new entry in DNS for TURN only, we'll use
`turn.example.com` here.

Make sure this entry only has an A record, no AAAA.

Get a certificate for this name:

```
certbot certonly --nginx -d turn.example.com
```

This assumes you've already setup and started nginx (see [nginx](../nginx)).

{#fixssl}
The certificate files reside under `/etc/letsencrypt/live`, but coturn and
LiveKit don't run as root, and can't read them. Therefore we create the directory
`/etc/coturn/ssl` where we copy the files to. This script should be run after
each certificate renewal:

```
#!/bin/bash

# This script is hooked after a renewal of the certificate, so that the
# certificate files are copied and chowned, and made readable by coturn:

cd /etc/coturn/ssl
cp /etc/letsencrypt/live/turn.example.com/{fullchain,privkey}.pem .
chown turnserver:turnserver *.pem

# Make sure you only start/restart the servers that you need!
systemctl try-reload-or-restart coturn livekit-server

```

Run this automatically after every renewal by adding this line to
`/etc/letsencrypt/renewal/turn.example.com.conf`:

```
renew_hook = /etc/coturn/fixssl
```

Yes, it's a bit primitive and could (should?) be polished. But for now: it
works. This will copy and chown the certificate files and restart coturn
and/or LiveKit, depending on if they're running or not.


# Configuration {#configuration}

Synapse's documentation gives a reasonable [default
config](https://element-hq.github.io/synapse/latest/setup/turn/coturn.html).

We'll need a shared secret that Synapse can use to control coturn, so let's
create that first:

```
pwgen -s 64 1
```

Now that we have this, we can configure our configuration file under
`/etc/coturn/turnserver.conf`.

```
# We don't use the default ports, because LiveKit uses those
listening-port=3480
tls-listening-port=5351

# We don't need more than 10000 connections:
min-port=40000
max-port=49999

use-auth-secret
static-auth-secret=<previously created secret>

realm=turn.example.com
user-quota=12
total-quota=1200

# Of course: substitute correct IPv4 address:
listening-ip=111.222.111.222

# VoIP traffic is only UDP
no-tcp-relay

# coturn doesn't run as root, so the certificate has
# to be copied/chowned here.
cert=/etc/coturn/ssl/fullchain.pem
pkey=/etc/coturn/ssl/privkey.pem

denied-peer-ip=0.0.0.0-255.255.255.255
denied-peer-ip=127.0.0.0-0.255.255.255
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.168.0.0-192.168.255.255
denied-peer-ip=100.64.0.0-100.127.255.255
denied-peer-ip=192.0.0.0-192.0.0.255
denied-peer-ip=169.254.0.0-169.254.255.255
denied-peer-ip=192.88.99.0-192.88.99.255
denied-peer-ip=198.18.0.0-198.19.255.255
denied-peer-ip=192.0.2.0-192.0.2.255
denied-peer-ip=198.51.100.0-198.51.100.255
denied-peer-ip=203.0.113.0-203.0.113.255

# We do only IPv4
allocation-default-address-family="ipv4"

# No weak TLS
no-tlsv1
no-tlsv1_1
```

All other options in the configuration file are either commented out, or
defaults.

Make sure you've opened the correct ports in the [firewall](../firewall).
