Coturn use-auth-secret: fixing the empty realm and repeated 401s

Repeated 401 responses can make a TURN server look as if it rejects every credential. On a server using use-auth-secret, the first question is not "is the password wrong?" It is whether coturn has a realm to challenge the client with at all.

Read the startup log before the request log

A useful log starts like this:

Default realm:
WARNING: CONFIG: you did specify the long-term credentials usage
but you did not specify the default realm option (-r option).
Check your configuration.

realm <> user <>: incoming packet message processed,
error 401: Unauthorized

use-auth-secret uses the long-term credential mechanism. A non-empty realm is part of the challenge. An empty realm means authentication cannot progress in the normal way, even if the client is sending a syntactically valid username and password.

Add a stable realm:

realm=turn.example.com

A hostname is convenient if the same server may later use TLS. If the deployment is intentionally IP-only, use a stable value consistently. Treat the realm as deployment configuration, not as a secret or a second password.

Make sure the configuration is actually loaded

Start the server with an absolute configuration path:

turnserver -c /root/turnserver.conf

The startup output should show a non-empty Default realm. This matters because a misspelled option or a different working directory can leave you editing one file while the process runs with another.

A single 401 on the first request is normal. The client is being challenged and is expected to retry with credentials. The symptom to fix is an endless stream of 401s that ends in a browser error such as:

TURN allocate request timed out.

Generate the REST credential exactly

For coturn's TURN REST mode, the username and password are:

username = <unix-expiry>:<label>
password = base64(HMAC-SHA1(secret, username))

A shell verification using the same secret is:

SECRET='mysecret'
EXP=$(($(date +%s) + 3600))
USER="$EXP:bob"
PASS=$(printf '%s' "$USER" | openssl dgst -binary -sha1 -hmac "$SECRET" | openssl base64)

printf 'username=%s\npassword=%s\n' "$USER" "$PASS"

The expiry must be in the future. The secret must contain exactly the bytes configured on the server, with no accidental trailing whitespace or quotes from a shell file. The password is the base64 representation of the HMAC result, not a direct HMAC string and not an MD5 digest.

Test with the Trickle ICE page

Create the credentials above, then paste them into the browser's Trickle ICE page with:

turn:12.34.567.89:3478?transport=udp

The useful result is a relay candidate. Seeing the TURN URL in the configuration is not enough. A STUN response from the same host also does not prove that TURN authentication works.

If the page finds a relay candidate but a real call still fails, keep the successful credential output and compare the exact values sent by the application. The browser may be generating a different username, expiry or password from the same secret.

Use a command-line client to remove the browser

After generating credentials, test the relay directly. The exact flags vary with the coturn build, so confirm them with turnutils_uclient -h, then run:

turnutils_uclient -v \
  -u "$USER" \
  -w "$PASS" \
  -p 3478 \
  12.34.567.89

If this succeeds and the browser does not, the relay is reachable and the remaining problem is in credential generation or browser configuration. If it fails, read the coturn response code instead of stopping at the browser timeout.

Read the response code, not just the symptom

If the server is behind one-to-one NAT, also set the public relay address correctly:

external-ip=12.34.567.89/10.0.0.4

Replace 10.0.0.4 with the local interface coturn sees. The relayed candidate must contain the public address. A successful allocation with a private relay candidate will still fail connectivity checks from the internet.

Check the UDP relay range in both firewalls

TURN authentication and media reachability are different tests. Even after the realm and credential generation are correct, RTP can fail if the relay range is closed.

min-port=49152
max-port=65535

Open that range over UDP in the provider security group and the host firewall. Port 3478 is not the media port. A successful TURN allocation is not proof that the relay path is usable.

Do not enable use-auth-secret and static users at the same time unless there is a deliberate reason. Pick one credential path, set its options explicitly and test it with a command-line TURN client before returning to the WebRTC application.

Minimal verification checklist

  1. Start coturn with an absolute config path.
  2. Confirm that Default realm is non-empty.
  3. Generate a future-dated REST credential with the configured secret.
  4. Use Trickle ICE and confirm that a relay candidate appears.
  5. Run a command-line TURN client and read the response code.
  6. Confirm external-ip and the UDP relay range from an outside network.

The empty realm is easy to miss because the client sees only the timeout. Fixing the realm does not automatically fix every TURN failure, but it removes the first blocker and makes the later credential and media tests meaningful.

If you want the working server, browser clients and deployment reference in one source package, see Low-Latency Kit.

Previous: coturn and Kurento in Docker -> All guides