TURN Server Cost Optimization: Reducing Your Relay Bandwidth Bill

When a WebRTC infrastructure bill starts climbing, the culprit is almost never CPU and almost always a TURN relay moving bytes it perhaps did not need to move. Signaling is tiny. STUN is a handful of packets. But a TURN server sits in the media path and forwards every frame of every relayed call, and bandwidth is what cloud providers charge real money for. The good news is that relay traffic is one of the most controllable line items in real-time communication — if you know how to measure it and where the waste hides.

This piece walks through the cost model, how to measure your own relay ratio, how to stop relaying calls that should have gone direct, and how the self-hosting and managed economics actually compare in 2026.

Why TURN costs money: every byte twice

A TURN server is a relay. A packet from peer A arrives at the server (ingress) and leaves toward peer B (egress). The same is true in reverse. So a relayed call pushes its entire media volume through the server, and on metered clouds you typically pay for the egress half of that — sometimes both halves.

Put numbers on it. A modest video call runs around 1.5 Mbps in each direction. Convert to bytes per hour for one direction:

1.5 Mbps = 1.5 / 8 = 0.1875 MB/s
0.1875 MB/s × 3600 s = 675 MB/h  ≈ 0.675 GB/h per direction

A call has two directions (A→B and B→A):
0.675 GB/h × 2 = 1.35 GB through the relay per hour of call

So one hour of one relayed 1.5 Mbps video call is roughly 1.35 GB of relay traffic. Ten thousand such call-hours in a month is 13.5 TB. At hyperscaler egress rates that is a four-figure bill for a single feature — which is why the rest of this article is about not relaying calls you do not have to.

First, measure your relay ratio

Where TURN cost comes from: the relay ratio Of all connections, most complete directly using STUN or host candidates and cost nothing to relay. Only the minority that fall back to TURN consume relay bandwidth, and because the server both receives and re-sends each byte, that traffic is billed twice. All connections direct — STUN / host ~80% · no relay cost relay ~20% · TURN your TURN bill relay GB × price × 2 Every relayed byte is billed twice — once in, once out. Halving the relay ratio nearly halves the bill. Percentages are illustrative — measure your own relay ratio before sizing anything.
Figure 1 — TURN cost is driven almost entirely by the small slice of connections that need a relay. Knowing that slice — your relay ratio — is the first step to controlling the bill.

Before optimizing anything, find out what fraction of your connections actually use TURN. For most consumer-web audiences only about 10–20% of connections need a relay; the rest succeed host-to-host or via server-reflexive (STUN) candidates. That number is not universal — enterprise networks behind strict firewalls, and mobile-heavy audiences stuck behind carrier-grade NAT, push it well higher — so measure your own traffic rather than trusting a blog's average (including this one).

The most accurate source is the client. Read the selected candidate pair from getStats() and report the relay flag back to your analytics:

const stats = await pc.getStats();
let localType, remoteType;
stats.forEach((report) => {
  if (report.type === 'candidate-pair' && report.nominated && report.state === 'succeeded') {
    const local = stats.get(report.localCandidateId);
    const remote = stats.get(report.remoteCandidateId);
    localType = local && local.candidateType;   // host | srflx | relay
    remoteType = remote && remote.candidateType;
  }
});
const usedRelay = localType === 'relay' || remoteType === 'relay';
analytics.track('ice_path', { localType, remoteType, usedRelay });

On the server side, coturn can tell you the same story in aggregate. Its logs record allocations and session byte counts, and the built-in Prometheus exporter exposes per-session and total traffic metrics you can graph. Cross-checking client candidate types against server byte totals also catches the worst case: relays carrying traffic for sessions that should have connected directly.

Make sure TURN isn't used unnecessarily

ICE already prefers cheaper paths. Each candidate carries a priority, and host and server-reflexive candidates are ranked above relay, so when a direct path works the connection checks pick it automatically. Your job is to not sabotage that.

The most expensive mistake is shipping iceTransportPolicy: 'relay' in production. That flag forces every connection through TURN, discarding all the direct paths ICE would have found. It is a useful diagnostic for confirming your relay works, but leaving it on in production means paying relay bandwidth for 100% of calls instead of 10–20%.

// FINE for a one-off relay test:
const pc = new RTCPeerConnection({ iceServers, iceTransportPolicy: 'relay' });

// WRONG for production — forces 100% of calls through TURN:
//   iceTransportPolicy: 'relay'

// CORRECT for production — let ICE choose; relay is the fallback:
const pc = new RTCPeerConnection({ iceServers }); // default 'all'

The quieter, costlier failure is broken STUN. If your STUN server is misconfigured or unreachable, clients never gather server-reflexive candidates, so peers that could have connected directly silently fall back to TURN — and your relay bill creeps up with no error anywhere. A working STUN setup is the single biggest lever for keeping relay traffic low, which is why verifying it is worth doing deliberately. Run your STUN and TURN URLs through the ICE Server Tester and confirm you actually get srflx candidates back; if you only ever see relay, STUN is the thing to fix first. For the underlying mechanics of when relaying is genuinely required, see our guide to TURN servers.

Self-hosting economics: it's bandwidth, not CPU

The defining fact of self-hosted TURN is that coturn is cheap to run and expensive to feed. A single modest VPS core can relay tens of thousands of concurrent allocations — packet forwarding is light work. What you pay for is the bandwidth flowing through it, and there the provider you choose can change the bill by an order of magnitude.

Hyperscalers meter egress aggressively. As of 2026, AWS and GCP list outbound transfer on the order of $0.08–0.12 per GB (tiered, region-dependent, and subject to change). Bandwidth-generous VPS providers price the opposite way: Hetzner, OVH, and similar hosts bundle large monthly transfer allowances — often tens of terabytes — into a flat server fee, with cheap or capped overage. For a workload that is almost pure egress, the difference is stark.

Scenario: 10 TB / month of TURN relay egress

Hyperscaler @ ~$0.10/GB:
  10,000 GB × $0.10 = ~$1,000 / month  (plus the instance)

Bandwidth-generous VPS:
  A mid-tier server with a ~20 TB transfer allowance
  often runs ~$5–$50 / month total, traffic included.

Those VPS numbers are illustrative and vary by plan and region, but the shape is real: for relay you are buying bandwidth, so host the relay where bandwidth is bundled, not where every gigabyte is metered. Put a tiny instance on a transfer-generous provider and you have turned a four-figure egress line into a flat monthly fee.

Managed TURN: the build-vs-buy math

If you would rather not operate relays, several providers sell TURN by the gigabyte or by subscription. Treat every figure below as approximate and 2026-dated — these prices move:

Provider Model (approx, 2026) Notes
Twilio NTS ~$0.40/GB historically Convenient, broad coverage; the priciest per GB
Cloudflare TURN On the order of cents/GB Markedly cheaper per GB; large edge footprint
Xirsys / Metered Subscription tiers + overage Predictable monthly plans; good for steady volume

Work an example at 10 TB/month of relay. At a Twilio-style ~$0.40/GB that is roughly $4,000; at a cents-per-GB service it can fall to the low hundreds; self-hosted on a transfer-generous VPS it approaches a flat double-digit fee plus your own operational time. The crossover is about volume and appetite for ops: managed wins at low or spiky volume where you do not want to babysit servers; self-hosting wins decisively once you are pushing many terabytes every month.

Regional placement: latency is a cost too

A relay adds a hop, so where it sits directly affects call quality. Routing a call between two users in Frankfurt through a relay in Virginia sends every packet across the Atlantic twice, adding well over a hundred milliseconds of round trip for no benefit. Relay near the users.

A sane starting point is one relay per major continent — for example North America, Europe, and Asia — with clients steered to the closest one. You can do this with GeoDNS resolving a single TURN hostname to the nearest region, or by handing each client a region-specific list of TURN URLs from your signaling server based on their source IP. Either way the principle holds: the nearest working relay is the cheapest in both latency and, often, inter-region transfer fees.

coturn tuning for cost and safety

An open or unbounded relay is a bandwidth liability — abusers will happily use your server to proxy their own traffic. A few coturn directives cap exposure and stop leeching:

# /etc/turnserver.conf

# --- Quotas: cap how much any user or the whole server can move ---
total-quota=1200            # max simultaneous allocations server-wide
user-quota=12               # max allocations per user
max-bps=2000000             # per-session cap, ~2 Mbps
bps-capacity=0              # 0 = unlimited aggregate; set to cap total

# --- Time-limited REST credentials (turn_secret + TTL) ---
use-auth-secret
static-auth-secret=REPLACE_WITH_LONG_RANDOM_SECRET
realm=turn.example.com

# --- Stop the relay from reaching internal/private networks ---
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.168.0.0-192.168.255.255
no-multicast-peers

# --- Metrics ---
prometheus

The single most important anti-abuse measure is time-limited credentials. With use-auth-secret and a shared static-auth-secret, your application mints short-lived usernames of the form timestamp:userid with an HMAC password, so a credential someone scrapes from your client expires in minutes instead of granting permanent free relay. Generate them like this:

# username = unix_expiry_timestamp ":" user_id
# password = base64( HMAC-SHA1( static_auth_secret, username ) )

EXPIRY=$(( $(date +%s) + 3600 ))         # valid 1 hour
USER="$EXPIRY:alice"
PASS=$(printf '%s' "$USER" \
  | openssl dgst -binary -sha1 -hmac "REPLACE_WITH_LONG_RANDOM_SECRET" \
  | openssl base64)
echo "username=$USER"
echo "credential=$PASS"

The denied-peer-ip ranges and no-multicast-peers prevent the classic open-relay attack where someone uses your TURN server to reach machines on your internal network. The quotas keep one abusive user from saturating the link, and prometheus gives you the per-session byte counts you need to actually see your relay ratio. Setup details are in our coturn installation guide.

Keep TCP/TLS as a last resort, not the default

TURN over UDP is the cheap, fast path. TURN over TCP, and especially TURN over TLS on port 443, cost more on both axes: TLS adds CPU for encryption, TCP's retransmission and head-of-line blocking add latency that hurts real-time media, and connection setup is heavier. You still need a 443/TLS listener — it is what gets calls through restrictive corporate firewalls that block UDP entirely — but it should be the fallback ICE reaches only after UDP and plain TCP fail, never the primary. Order your iceServers so the UDP turn: URL comes first and the turns: on 443 comes last, and the engine will prefer the cheaper transport whenever the network allows it.

Summary: the cost-reduction checklist

Action Why it cuts cost
Measure relay ratio via getStats / coturn metrics You can't optimize what you haven't quantified; reveals waste
Verify STUN actually returns srflx candidates Broken STUN silently dumps direct-capable calls onto TURN
Never ship iceTransportPolicy: 'relay' Forces 100% of calls through paid relay instead of ~10–20%
Self-host on a transfer-generous VPS Relay is bandwidth, not CPU; avoids metered egress rates
Place a relay per continent Cuts cross-region latency and inter-region transfer fees
Time-limited REST credentials + quotas Stops credential leeching and open-relay bandwidth theft
denied-peer-ip + no-multicast-peers Blocks relay abuse toward internal networks
UDP first, TLS/443 last Avoids extra CPU and latency of TCP/TLS relay when avoidable

Do the first three and most teams cut their relay traffic substantially overnight, because the common case is misconfiguration quietly routing direct-capable calls through TURN — not genuine demand for relaying. The rest is about hosting the unavoidable relay traffic as cheaply and safely as possible. If you are still choosing a media topology, our SFU vs MCU vs mesh comparison covers how architecture choices upstream of TURN also shape your bandwidth bill.

Check Your Own Servers

Misconfigured STUN is the quietest cause of a runaway TURN bill — it pushes direct-capable calls onto paid relay with no error. Run your STUN and TURN URLs through the tester and confirm you're getting srflx candidates, not just relay.

Open the Free ICE Server Tester →