[TURN][turn] and [STUN][stun] are used as a component in many calling systems. Matrix uses them directly for legacy calls and indirectly for MatrixRTC via Livekit.
Next, we will start the Coturn container with the [official image][coturn-image]. **Host networking mode** will be used, as it is better for performance and reduces configuration complexity (see [Coturn Docker docs][coturn-docker-docs] for rationale).
Get an access token for your current login session. These can be found in your client's settings or obtained via [this website](https://timedout.uk/mxtoken.html).
Then, using that token, verify that Continuwuity is correctly serving TURN credentials to clients:
- Verify firewall rules allow the necessary ports (3478, 5349, and your media port range)
- Check that DNS resolves correctly for your TURN domain
- `code=401` - unauthorized credentials
- Ensure your `turn_secret` matches coturn's `static-auth-secret`
- Ensure the credentials you obtained from the Testing steps has not expired yet. You can adjust `turn_ttl` in your Continuwuity configuration to increase this, or simply re-request a new one
This is the correct response when no TURN servers are configured, as per [MSC4166][msc4166]. Verify that your `turn_uris` is not empty in your Continuwuity config and try again.
In very restrictive networks where UDP traffic and non-standard ports are disallowed, normal TURN servers are not reachable. To establish connectivity in these scenarios, a common solution is to host a TURN-over-TLS server on port 443, and allow clients to connect to it like any other web traffic.
However, port 443 is usually utilized by other HTTPS services. Therefore, one would need to **multiplex** both the TURN and the HTTPS services, and filter packets to them via **SNI routing**. A layer-4 load balancer, such as [caddy-l4][caddy-l4], can serve this function.
Below is an example of using Caddy-l4 on the host to:
- Route `turn.example.com` to the TURNS port for Coturn without TLS termination, and
- Route `livekit.example.com` to the [LiveKit services](./livekit.mdx) with TLS termination by Caddy
```
{
servers {
listener_wrappers {
# intercept packets meant for the TURN domain first
# before forwarding other packets to "normal" HTTP listeners
layer4 {
@turn {
tls {
sni turn.example.com
}
}
route @turn {
proxy {
upstream 127.0.0.1:5349 # forward to normal TURNS port
}
}
}
tls
}
}
}
# livekit stuff
https://livekit.example.com {
@lk-jwt-service path /healthz /get_token /sfu/get
route @lk-jwt-service {
reverse_proxy 127.0.0.1:8081
}
reverse_proxy http://127.0.0.1:7880
}
```
You can now advertise `turns:turn.example.com:443?transport=tcp` as an address in your `turn_uris` as well as LiveKit. Please note that all traffic from Coturn's perspective will be coming from caddy-l4's IP now.
These TURN setups are available, but **not recommended** due to security issues. They are only included for completeness.
<details>
<summary>Using static credentials</summary>
If you prefer static username/password credentials instead of shared secrets:
```ini
# In coturn.conf
# Comment out options to use a secret
# use-auth-secret
# static-auth-secret=<a secret key>
# Define a username-password pair
user=your_username:your_password
```
```toml
# In continuwuity.toml
turn_uris = [
"turn:coturn.example.com?transport=udp",
"turn:coturn.example.com?transport=tcp"
]
turn_username = "your_username"
turn_password = "your_password"
```
:::caution
Static credentials are less secure than shared secrets because they don't expire and must be configured in coturn separately. It is strongly advised you use shared secret authentication.
:::
</details>
<details>
<summary>Allowing guest access</summary>
By default, TURN credentials require client authentication. To allow unauthenticated access:
```toml
turn_allow_guests = true
```
:::caution
This is not recommended as it allows unauthenticated users to access your TURN server, potentially enabling abuse by bots. All major Matrix clients that support legacy calls *also* support authenticated TURN access.