Skip to content

Build1 publisher2 min readPublished

The selected candidate pair decides whether you are debugging coturn at all

A coturn install on Debian can report active, bind nothing, and log no error. A dev.to walkthrough therefore puts two browser checks ahead of any config edit, so you stop fixing a relay that already works.

The Engineer · Build desk

Illustration accompanying The selected candidate pair decides whether you are debugging coturn at all

What happened

  • A dev.to walkthrough of self-hosted coturn starts debugging in the browser: open chrome://webrtc-internals or about:webrtc, find the selected candidate pair, and read the local candidate's type.
  • The post replaces the liveness question with a bind question: run ss -lunp against 3478 and 5349, then read the last 50 journalctl lines for the coturn unit.
  • In the second failure the allocation succeeds and the relayed candidate comes back as a 10.x or 172.16-31.x address, ICE never gets past checking, and the server side stays green throughout.
  • The cause is a cloud VM holding a private address on its interface while the public IP lives in the provider's NAT layer, leaving coturn to advertise the address it is bound to.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint Monitoring coturn on systemctl status buys nothing, because the post notes a service that declined to start and one that started and could not bind look the same there. The check can be green while no media is relayed at all.
  • decision Whether to add a relay at all is now the first decision. Bolting TURN onto a system whose real fault is elsewhere conceals that fault and makes the next debugging session harder.
  • cost The public-only external-ip half-fix defers its failure to whenever coturn next needs to bind locally. The engineer who pays for it is whoever is on call then, holding a config that has worked for weeks.
  • capability Two of the three checks need only a browser, so support can establish whether the relay is even involved before anyone with SSH access is woken up.

A local candidate of type `relay` in the selected pair means the browser allocated on your TURN server and got back an address the far side could reach, so the relay is in the path and the fault is elsewhere [3]. If the type is `srflx` or `host` and the call still fails, the relay was never asked to do anything, and the bug lives in your ICE configuration [4].

Building the peer connection with `iceTransportPolicy: 'relay'` in a test page removes every non-relay path, so TURN is the only thing left to try [5]. Connects under forced relay, fails under your normal config: the relay is fine and candidate selection is the fault [5].

Ordering matters because `systemctl` cannot separate the two server-side failures underneath it. On Debian and Ubuntu the package ships `/etc/default/coturn` with the enable switch commented out, and depending on version and install method the gate is the init script or a unit wrapping it, which exits clean with no error either way [7][8]. The dev.to post writes that a service that declined to start and one that started and could not bind "look identical from systemctl" [9]. Setting `TURNSERVER_ENABLED=1` and restarting turns the silence into a real startup error you can read in the log [13].

The same silence covers a config-path mismatch: coturn reads `/etc/turnserver.conf` unless something passes `-c`, and when it does, your edits are being ignored with nothing to say so [10]. The `curl` check feels like a test, but TURN is not HTTP, and a request to 3478 will connect and then produce something unhelpful [12].

Both halves of the address mapping go on one line, public first, private second: `external-ip=203.0.113.10/10.0.0.4` [16]. Setting only the public half is the common version, and it holds until coturn needs to bind locally [16]. A public address in the candidate and a reachable one are separate claims, so re-read the relayed candidate after the change [17].

Before any of that, rule out the cheap two. A server advertising an unroutable address and a closed UDP media range each produce the outside view of a dead relay: green on the server, ICE stuck in checking before it fails. Both are more common than a broken coturn [6]. 3478 is the control port [18].

What to watch

  • The post's third failure section breaks off after naming 3478 the control port, so the UDP media range it prescribes is not in the supplied text.
  • Whether a later coturn package on Debian or Ubuntu drops the /etc/default/coturn gate. Dropping it would retire the commonest silent non-start.
  • Whether cloud images start shipping coturn with external-ip pre-populated from instance metadata instead of leaving it to the operator.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories