# Setting up TURN/STUN

[TURN](https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT) and [STUN](https://en.wikipedia.org/wiki/STUN) are used as a component in many calling systems. Matrix uses them directly for legacy calls and indirectly for MatrixRTC via Livekit.

Continuwuity recommends using [Coturn](https://github.com/coturn/coturn) as your TURN/STUN server, which is available as a Docker image or a distro package.

## Installing Coturn

### Configuration

Create a configuration file called `coturn.conf` containing:

```ini
use-auth-secret
static-auth-secret=<a secret key>
realm=<your server domain>
```

:::tip Generating a secure secret
A common way to generate a suitable alphanumeric secret key is by using:
```bash
pwgen -s 64 1
```
:::

#### Port Configuration

By default, coturn uses the following ports:
- `3478` (UDP/TCP): Standard TURN/STUN port
- `5349` (UDP/TCP): TURN/STUN over TLS
- `49152-65535` (UDP): Media relay ports

If you're also running LiveKit, you'll need to avoid port conflicts. Configure non-overlapping port ranges:

```ini
# In coturn.conf
min-port=50201
max-port=65535
```

This leaves ports `50100-50200` available for LiveKit's default configuration.

### Running with Docker

Run the [Coturn](https://hub.docker.com/r/coturn/coturn) image using:

```bash
docker run -d --network=host \
  -v $(pwd)/coturn.conf:/etc/coturn/turnserver.conf \
  coturn/coturn
```

### Running with Docker Compose

Create a `docker-compose.yml` file and run `docker compose up -d`:

```yaml
version: '3'
services:
  turn:
    container_name: coturn-server
    image: docker.io/coturn/coturn
    restart: unless-stopped
    network_mode: "host"
    volumes:
      - ./coturn.conf:/etc/coturn/turnserver.conf
```

:::info Why host networking?
Coturn uses host networking mode because it needs to bind to multiple ports and work with various network protocols. Using host networking is better for performance, and reduces configuration complexity. To understand alternative configuration options, visit [Coturn's Docker documentation](https://github.com/coturn/coturn/blob/master/docker/coturn/README.md).
:::

### Security Recommendations

For security best practices, see Synapse's [Coturn documentation](https://element-hq.github.io/synapse/latest/turn-howto.html), which includes important firewall and access control recommendations.

## Configuring Continuwuity

Once your TURN server is running, configure Continuwuity to provide credentials to clients. Add the following to your Continuwuity configuration file:

### Shared Secret Authentication (Recommended)

This is the most secure method and generates time-limited credentials automatically:

```toml
# TURN URIs that clients should connect to
turn_uris = [
    "turn:coturn.example.com?transport=udp",
    "turn:coturn.example.com?transport=tcp",
    "turns:coturn.example.com?transport=udp",
    "turns:coturn.example.com?transport=tcp"
]

# Shared secret for generating credentials (must match coturn's static-auth-secret)
turn_secret = "<your coturn static-auth-secret>"

# Optional: Read secret from a file instead (takes priority over turn_secret)
# turn_secret_file = "/etc/continuwuity/.turn_secret"

# TTL for generated credentials in seconds (default: 86400 = 24 hours)
turn_ttl = 86400
```

:::tip Using TLS
The `turns:` URI prefix instructs clients to connect to TURN over TLS, which is highly recommended for security. Make sure you've configured TLS in your coturn server first.
:::

### Static Credentials (Alternative)

If you prefer static username/password credentials instead of shared secrets:

```toml
turn_uris = [
    "turn:coturn.example.com?transport=udp",
    "turn:coturn.example.com?transport=tcp"
]

turn_username = "your_username"
turn_password = "your_password"
```

:::warning
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.
:::

### Guest Access

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.
:::

### Important Notes

- Replace `coturn.example.com` with your actual TURN server domain (the `realm` from coturn.conf)
- The `turn_secret` must match the `static-auth-secret` in your coturn configuration
- Restart or reload Continuwuity after making configuration changes

## Testing Your TURN Server

### Testing Credentials

Verify that Continuwuity is correctly serving TURN credentials to clients:

```bash
curl "https://matrix.example.com/_matrix/client/r0/voip/turnServer" \
  -H "Authorization: Bearer <your_client_token>" | jq
```

You should receive a response like this:

```json
{
  "username": "1752792167:@jade:example.com",
  "password": "KjlDlawdPbU9mvP4bhdV/2c/h65=",
  "uris": [
    "turns:coturn.example.com?transport=udp",
    "turns:coturn.example.com?transport=tcp",
    "turn:coturn.example.com?transport=udp",
    "turn:coturn.example.com?transport=tcp"
  ],
  "ttl": 86400
}
```

:::note MSC4166 Compliance
If no TURN URIs are configured (`turn_uris` is empty), Continuwuity will return a 404 Not Found response, as specified in MSC4166.
:::

### Testing Connectivity

Use [Trickle ICE](https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/) to verify that the TURN credentials actually work:

1. Copy the credentials from the response above
2. Paste them into the Trickle ICE testing tool
3. Click "Gather candidates"
4. Look for successful `relay` candidates in the results

If you see relay candidates, your TURN server is working correctly!

## Troubleshooting

### Clients can't connect to TURN server

- Verify firewall rules allow the necessary ports (3478, 5349, and your media port range)
- Check that DNS resolves correctly for your TURN domain
- Ensure your `turn_secret` matches coturn's `static-auth-secret`
- Test with Trickle ICE to isolate the issue

### Port conflicts with LiveKit

- Make sure coturn's `min-port` starts above LiveKit's `port_range_end` (default: 50200)
- Or adjust LiveKit's port range to avoid coturn's default range

### 404 when calling turnServer endpoint

- Verify that `turn_uris` is not empty in your Continuwuity config
- This behavior is correct per MSC4166 if no TURN URIs are configured

### Credentials expire too quickly

- Adjust the `turn_ttl` value in your Continuwuity configuration
- Default is 86400 seconds (24 hours)

### Related Documentation

- [MatrixRTC/LiveKit Setup](./livekit.mdx) - Configure group calling with LiveKit
- [Coturn GitHub](https://github.com/coturn/coturn) - Official coturn repository
- [Synapse TURN Guide](https://element-hq.github.io/synapse/latest/turn-howto.html) - Additional security recommendations
