batondocs← SiteSign in
Migrating/From self-hosted coturn

Migrating from self-hosted coturn

You're running coturn yourself — a relay process, a credential-minting endpoint, TLS certs, and firewall/ACL upkeep. Moving to Baton replaces all of that with one API call. Your client code barely changes; the only real work is swapping how credentials are issued.

Before you start

A typical self-hosted setup has three parts: the coturn process (with a static-auth-secret), a small backend endpoint that mints time-limited TURN credentials from that secret, and clients that fetch those credentials and build an iceServers array. You'll keep the client, replace the backend endpoint, and retire coturn.

Grab a project API key from the dashboard before step 1.

Step 1 — Replace credential minting

Today your endpoint computes a username/credential from your local secret. Baton issues those for you and returns a complete, multi-transport iceServers array — so the endpoint gets shorter. Drop the HMAC code; call the API instead.

// you mint REST credentials from coturn's static-auth-secret
app.get("/api/ice", (req, res) => {
  const expiry = Math.floor(Date.now() / 1000) + 3600;
  const username = `${expiry}:${req.user.id}`;
  const hmac = crypto.createHmac("sha1", process.env.TURN_STATIC_SECRET);
  hmac.update(username);
  const credential = hmac.digest("base64");
  res.json({ iceServers: [{
    urls: ["turn:turn.example.com:3478", "turns:turn.example.com:443?transport=tcp"],
    username, credential
  }]});
});

Step 2 — Leave the client alone

Your client already fetches iceServers from that endpoint and hands it to RTCPeerConnection — that doesn't change. If you ever hardcoded coturn URLs in the client, delete them and rely on the array from your endpoint, which now carries all four Baton transports.

client.js — unchanged
const { iceServers } = await fetch("/api/ice").then(r => r.json());
const pc = new RTCPeerConnection({ iceServers });

Step 3 — Run in parallel and verify

  1. Roll the new endpoint out gradually

    Point a fraction of traffic at the Baton-backed endpoint while coturn keeps serving the rest. Both can run side by side — there's no client release to coordinate.

  2. Confirm relay candidates come from Baton

    In chrome://webrtc-internals, check for a selected candidate pair with typ relay at relay.usebaton.io — especially over turns/443. See Troubleshooting if you only see host/srflx.

  3. Watch a real call end to end

    Verify media flows on a connection that previously required your coturn relay (a strict-firewall client is the best test).

Step 4 — Decommission coturn

Once Baton is carrying traffic cleanly, retire the old stack. You stop running and maintaining:

  • The coturn process and its public IP/ports.
  • The static-auth-secret and your credential-minting HMAC code.
  • TLS certificates for the TURN listener and their renewal.
  • denied-peer-ip / relay ACLs and capacity planning — Baton handles these.