# coturn (TURN/STUN) config for the EarthmanRPG WebRTC mesh voice feature — # Phase C of the my-sea invite/voice sprint. Rendered by coturn-playbook.yaml # onto a DEDICATED droplet (PySwiss-style split), NOT the app droplet. # # The app's /api/voice/turn-credentials/ endpoint signs ephemeral credentials # with HMAC-SHA1(:, secret); `use-auth-secret` + # `static-auth-secret` here must use the SAME secret (COTURN_SHARED_SECRET in # the app env). listening-port=3478 tls-listening-port=5349 fingerprint lt-cred-mech use-auth-secret static-auth-secret={{ coturn_secret }} realm={{ coturn_realm }} # ── THE #1 FOOTGUN ────────────────────────────────────────────────────────── # Without external-ip, coturn hands out its PRIVATE address as the relay # candidate and every relayed connection silently fails. On a DigitalOcean # droplet with a single public IP set it to that IP; if the droplet also has a # private/anchor IP, use PUBLIC/PRIVATE so coturn maps between them. external-ip={{ coturn_public_ip }}{% if coturn_private_ip is defined and coturn_private_ip %}/{{ coturn_private_ip }}{% endif %} {% if coturn_public_ip6 is defined and coturn_public_ip6 %} # Dual-stack: advertise IPv6 relay candidates too. coturn auto-binds all # interfaces (incl. v6) since no listening-ip is pinned; this maps the public # v6 explicitly. Set coturn_public_ip6 in inventory to enable — leave it unset # for a pure-IPv4 server (the v6 peer-lockdown below is gated on the same var). external-ip={{ coturn_public_ip6 }} {% endif %} # Relay port range — open this exact UDP range in the firewall (playbook does). min-port=49152 max-port=65535 # ── TLS (turns: on 5349) — prod hardening ────────────────────────────────── {% if coturn_tls_cert is defined and coturn_tls_cert %} cert={{ coturn_tls_cert }} pkey={{ coturn_tls_key }} {% endif %} no-tlsv1 no-tlsv1_1 # ── Lockdown: relay only, no SSRF via the TURN server ─────────────────────── no-multicast-peers no-cli no-software-attribute # Block relaying to private ranges so the box can't be used to probe internals. 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 denied-peer-ip=127.0.0.0-127.255.255.255 {% if coturn_public_ip6 is defined and coturn_public_ip6 %} # IPv6 lockdown parity (only emitted when serving v6): loopback, link-local # (fe80::/10), and unique-local (fc00::/7). coturn takes start-end ranges, not # CIDR. Keeps a dual-stack relay from being pointed at internal v6 addresses. denied-peer-ip=::1 denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff {% endif %} log-file=/var/log/turnserver/turn.log simple-log