Using ExpressTURN with LiveKit

Configure once in livekit.yaml, every client gets the relay automatically.

Server-side: livekit.yaml (recommended)

LiveKit hands its TURN server list to clients automatically. Configure ExpressTURN once on the server and every joining participant gets it.

rtc:
  use_external_ip: true
  turn_servers:
    - host: relay1.expressturn.com
      port: 3478
      protocol: udp
      username: YOUR_EXPRESSTURN_USERNAME
      credential: YOUR_EXPRESSTURN_PASSWORD
    - host: relay1.expressturn.com
      port: 3478
      protocol: tcp
      username: YOUR_EXPRESSTURN_USERNAME
      credential: YOUR_EXPRESSTURN_PASSWORD
    - host: relay1.expressturn.com
      port: 443
      protocol: tls
      username: YOUR_EXPRESSTURN_USERNAME
      credential: YOUR_EXPRESSTURN_PASSWORD

Client-side override (optional)

If you'd rather mint per-session credentials and ship them with the access token, override rtcConfig when creating the LiveKit Room:

// npm install livekit-client
import { Room } from 'livekit-client';

const room = new Room({
  rtcConfig: {
    iceServers: [
      { urls: 'stun:stun.expressturn.com:3478' },
      {
        urls: [
          'turn:relay1.expressturn.com:3478?transport=udp',
          'turn:relay1.expressturn.com:3478?transport=tcp',
          'turns:relay1.expressturn.com:443?transport=tcp'
        ],
        username: 'YOUR_EXPRESSTURN_USERNAME',
        credential: 'YOUR_EXPRESSTURN_PASSWORD'
      }
    ],
    iceTransportPolicy: 'all'
  }
});

await room.connect(LIVEKIT_URL, accessToken);

LiveKit Cloud users

LiveKit Cloud already includes TURN. If you're hitting bandwidth limits on Cloud or want a cheaper relay for self-hosted regions, you can list ExpressTURN as an additional turn_server in your client's rtcConfig — ICE picks the best path.

Force-relay validation

const room = new Room({
  rtcConfig: { iceServers: [...], iceTransportPolicy: 'relay' }
});

Premium: shared-secret per-session creds

For production deployments, mint short-lived TURN credentials in your token-generation endpoint and ship them alongside the LiveKit access token. See shared-secret examples for the HMAC code.

Common pitfalls

  • Both server and client config: If you set turn_servers in livekit.yaml AND rtcConfig on the client, the client config wins. Pick one.
  • Missing TLS-443: Strict corporate firewalls block 3478. The TLS path on 443 is what saves connections.
  • External IP discovery: Make sure rtc.use_external_ip is set so LiveKit advertises a reachable host candidate; without it, more clients fall back to TURN than necessary.

Related: mediasoup recipe · Janus recipe · TURN for live streaming

Done.

Get Free TURN Credentials