Setting up TURN/STUN

TURN and 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 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:

use-auth-secret
static-auth-secret=<a secret key>
realm=<your server domain>
Generating a secure secret

A common way to generate a suitable alphanumeric secret key is by using:

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:

# 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 image using:

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:

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

Security Recommendations

For security best practices, see Synapse's Coturn documentation, 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:

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

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

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:

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:

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

You should receive a response like this:

{
  "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
}
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 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)