From c11f05481c3cfef000e1da2a32543d918dad02d5 Mon Sep 17 00:00:00 2001 From: mguschin Date: Tue, 18 Aug 2026 22:20:37 +0300 Subject: [PATCH] Fix coturn TLS listeners and pin relay to public IP. Two problems found once coturn was running: TLS listeners never started. The live/ certificate files are relative symlinks into ../../archive/, so mounting live/ alone left them dangling and coturn silently fell back to no TLS, disabling turns: on 5349. Prosody's identical mount works only because its entrypoint copies the files; coturn reads them in place. Mount both trees at their real paths and reference the cert through live/. Relay used every interface. Without explicit addresses coturn discovered all of them and offered relay candidates on the Docker bridges and loopback -- unreachable for remote peers, and needless exposure of the internal networks. Pin listening-ip and relay-ip to the public address. Co-Authored-By: Claude Opus 5 --- README.md | 8 +++++--- coturn/turnserver.conf | 11 +++++++++-- docker-compose.yml | 6 +++++- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7e43519..e897201 100644 --- a/README.md +++ b/README.md @@ -112,9 +112,11 @@ the current cert's SANs. To use `turn.guschin.info` instead, reissue the certificate with that name added and update both `TURN_HOST` and the `external-ip`/`cert` settings accordingly. -`external-ip` in `turnserver.conf` is the server's public address. **Update it -if the server IP changes**, otherwise coturn advertises relay candidates that -peers cannot reach. +`turnserver.conf` pins the server's public address in three places: +`listening-ip`, `relay-ip`, and `external-ip`. **Update all three if the +server IP changes**, otherwise coturn advertises relay candidates that peers +cannot reach. Pinning them also stops coturn from auto-discovering the Docker +bridge interfaces and relaying on unreachable private addresses. ### Verifying diff --git a/coturn/turnserver.conf b/coturn/turnserver.conf index 9e8be8f..83da6d8 100644 --- a/coturn/turnserver.conf +++ b/coturn/turnserver.conf @@ -10,6 +10,13 @@ listening-port=3478 tls-listening-port=5349 +# Pin both the listening and relay addresses to the public interface. +# Without these, coturn auto-discovers every interface and hands out relay +# candidates on the Docker bridges (172.x, 192.168.x) and loopback, which +# remote peers cannot reach and which needlessly expose internal networks. +listening-ip=176.124.216.197 +relay-ip=176.124.216.197 + # Public address advertised in relay candidates. Without this, coturn hands # out its own view of the interface address, which breaks behind any NAT. external-ip=176.124.216.197 @@ -27,8 +34,8 @@ realm=guschin.info # TLS for turns:. Uses the existing multi-SAN guschin.info certificate, which # is why the advertised TURN host is guschin.info and not turn.guschin.info # (the latter is not in the cert's SANs, so TLS validation would fail). -cert=/etc/coturn/certs/fullchain.pem -pkey=/etc/coturn/certs/privkey.pem +cert=/etc/letsencrypt/live/guschin.info/fullchain.pem +pkey=/etc/letsencrypt/live/guschin.info/privkey.pem # Harden: this is a relay for our own users, not an open proxy. # Deny relaying to private ranges so TURN can't be used to reach internal diff --git a/docker-compose.yml b/docker-compose.yml index 77501c4..cafb576 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -69,7 +69,11 @@ services: network_mode: host volumes: - ./coturn/turnserver.conf:/etc/coturn/turnserver.conf:ro - - /etc/letsencrypt/live/guschin.info:/etc/coturn/certs:ro + # The live/ files are relative symlinks into ../../archive/, so both + # trees must be mounted at their real paths for the links to resolve. + # coturn reads the certificate directly (unlike Prosody, whose + # entrypoint copies it), so a broken symlink silently disables TLS. + - /etc/letsencrypt/live/guschin.info:/etc/letsencrypt/live/guschin.info:ro - /etc/letsencrypt/archive/guschin.info:/etc/letsencrypt/archive/guschin.info:ro command: ["-c", "/etc/coturn/turnserver.conf"] restart: unless-stopped