Livqeno Docs

TURN & NAT traversal

Why a relay is not optional, how credentials are minted, and the configuration that gets it wrong.

TURN is the difference between calls that work everywhere and calls that work on your laptop.

Why it is not optional

Two clients can only exchange media directly if the network lets them. On a corporate network, a symmetric NAT, or a mobile carrier, it often does not. A TURN server relays the media instead.

Without a working relay, a share of your users — the share you cannot predict — get calls that connect and then carry nothing.

How credentials work

You do not issue TURN credentials. Livqeno does, per token:

POST /v1/rooms/{roomId}/rtc-tokens
   → { token, endpoint, iceServers: [ … ], … }

iceServers carries STUN and TURN entries with a username and credential minted from TURN_SECRET and scoped to that token's lifetime.

Forward it untouched. No client should ever hold a long-lived relay credential, and nothing should hand-build this array:

const client = createRTCClient({
  token: credentials.token,
  endpoint: credentials.endpoint,
  iceServers: credentials.iceServers,   // as-is
});

Configuration

VariableWhat it is
TURN_HOSTHost-facing address clients will actually dial
TURN_PORTUsually 3478
TURN_TLS_PORTOnly set it if coturn has a real certificate; advertises turns:
TURN_SECRETShared secret credentials are derived from
TURN_INTERNAL_HOSTThe address the API's own health probe uses — the Docker service name

TURN_HOST and TURN_INTERNAL_HOST are separate on purpose and mixing them up is the most common failure. The health check runs inside the API container, so it wants coturn. A browser wants a public hostname. Put a container name in TURN_HOST and every client silently fails to reach the relay while your health check stays green.

Ports

Open at the firewall:

  • 3478/udp and 3478/tcp — TURN and STUN.
  • 5349/tcpturns: over TLS, if configured.
  • The relay range/udp — the ports coturn hands out for actual media. A relay range that is closed means allocation succeeds and media does not arrive, which is a genuinely confusing failure.

Verify, do not assume

raven diagnostics

Reports whether the API can reach each dependency, TURN included. For the media path itself, the repository ships a relay test:

scripts/turn-relay-test.sh                    # TURN over UDP
scripts/turn-relay-test.sh --transport tcp    # TURN over TCP

That forces two clients to be relay-only and pushes real RTP through coturn — the only evidence that counts.

Verified and not verified

Stated because "TURN works" is a claim that needs evidence:

PathStatus
TURN relay over UDPTested, automated
TURN relay over TCPTested, automated
turns: over TLS, end to endNot tested — the local certificate is self-signed, which Pion rejects for the same reason a browser would. Server-side TLS is verified separately
Symmetric NAT on both sidesRelay path tested; the NAT itself is not — relay-only is forced by policy, not by a NAT that left no alternative

A relay that fails open is worse than one that fails

coturn's behaviour with an unreadable configuration file is to start without it — which can mean an open relay. Treat a coturn config parse error as a hard failure in your own deployment tooling. See Known limitations.

Next steps