Media relay (TURN)
By default Monky's voice and video travel straight between participants (P2P). The server only handles the introductions. That is a good thing: less latency, and almost no bandwidth for whoever hosts.
The problem can show up when members sit behind CGNAT — common on mobile networks and at many residential ISPs. Depending on the NAT and firewall, the two sides may be unable to establish a direct route, even though each of them connects fine with everyone else.
TURN offers an alternative path: the server forwards that pair's media. WebRTC tries direct connectivity when possible; the relay must also be reachable with the correct ports and credentials.
This page applies to P2P Mesh mode
In SFU mode every participant sends media to the server rather than directly to other people. Monky's integrated TURN is offered only for P2P: the app and CLI reject enabling it alongside SFU. This does not remove the need for a reachable server. If SFU media does not flow, check the announced IP, process and SFU ports instead of attempting an unsupported combination.
Requirements
- A Linux host with a public IP (a typical VPS). Monky manages this relay's installation and execution only on Linux; it does not offer that control on Windows or macOS.
- Open ports in the firewall (see below).
- Bandwidth on the host: every relayed pair uses the server's upload and download.
Required ports
| Port | Protocol | Purpose |
|---|---|---|
3478 | TCP | TURN listening port (signaling and allocate) |
3478 | UDP | TURN listening port (signaling and allocate) |
49152-65535 | UDP | Port range for media relay |
An online process does not prove the media path
Allow the configured transports above to support differently restricted networks. Allocation and traffic are separate stages: port 3478 can respond while the relay range remains blocked. Check media playback, not just whether the process is running.
Opening ports on Linux
1. Provider firewall (web panel)
Most VPS providers (Oracle Cloud, AWS, Azure, GCP, Hetzner) have a firewall outside the machine, at the network level. This firewall must be configured in the provider's web panel — changing iptables alone is not enough.
Oracle Cloud (OCI)
- Open the Oracle Cloud console
- Go to Networking → Virtual Cloud Networks
- Click on the VCN of your instance
- In the side menu, click Security (or Subnets → your subnet → Security List)
- Click on the associated Security List
- Click Add Ingress Rules and add 3 rules:
| Source CIDR | IP Protocol | Destination Port Range |
|---|---|---|
0.0.0.0/0 | UDP | 3478 |
0.0.0.0/0 | TCP | 3478 |
0.0.0.0/0 | UDP | 49152-65535 |
- Save
AWS (EC2)
- Open the AWS console → EC2 → Security Groups
- Select your instance's Security Group
- Tab Inbound rules → Edit inbound rules
- Add the same 3 rules (Custom UDP/TCP, source
0.0.0.0/0)
Other providers
Look for "Security Groups", "Firewall Rules" or "Network ACL" in your provider's panel. The logic is the same: open ports 3478 TCP/UDP and the range 49152-65535 UDP from any source.
2. Linux firewall (iptables)
Even with the provider firewall open, Linux may have its own rules. Run:
# Open the ports
sudo iptables -I INPUT -p udp --dport 3478 -j ACCEPT
sudo iptables -I INPUT -p tcp --dport 3478 -j ACCEPT
sudo iptables -I INPUT -p udp --dport 49152:65535 -j ACCEPT
# Persist (survives reboot)
sudo netfilter-persistent saveThis command assumes netfilter-persistent is installed and configured. On other distributions, use the local firewall's persistence mechanism.
If you use ufw instead of iptables
sudo ufw allow 3478/tcp
sudo ufw allow 3478/udp
sudo ufw allow 49152:65535/udpIf you use firewalld (CentOS/RHEL)
sudo firewall-cmd --permanent --add-port=3478/tcp
sudo firewall-cmd --permanent --add-port=3478/udp
sudo firewall-cmd --permanent --add-port=49152-65535/udp
sudo firewall-cmd --reloadEnabling the relay
Do not reuse another service's coturn
Monky starts its own instance. Automatic installation and the repository helper attempt to disable the distribution's coturn service to free port 3478. If another application relies on that service, plan a separate host before continuing; do not stop a shared relay.
monky stop
monky config set turn true
monky startMonky attempts to install coturn through the distribution's package manager when needed. This requires root or previously authorized non-interactive sudo; the process does not open a password prompt. You can also enable it under Server Settings → Voice & Video in the app.
Manual coturn installation
Without those privileges, prepare the host first. On Debian/Ubuntu with systemd, on a host dedicated to Monky's relay:
sudo apt-get update
sudo apt-get install -y coturn
sudo systemctl disable --now coturn
command -v turnserverOn other distributions, use their coturn package and service manager. Then return to monky stop, monky config set turn true and monky start as the same user who manages the server, without running all of Monky as root.
If you cloned the repository, there is also a helper. Run it from the root of that checkout:
sudo bash scripts/install-turn.shThis file is not included in the global CLI installation. That path does not work from an arbitrary directory after npm install -g.
Checking that it works
Via CLI
monky statusIn the TURN Relay section, you should see:
turn: yes
coturn: installed
port: 3478
status: ✔ accessibleIf you see ⚠ port blocked, review the firewalls above.
Via network (from another machine)
# Check if the port is responding
nc -zv YOUR_IP 3478This tests only the TCP connection. It does not confirm UDP transport, the relay port range or a complete call between two networks.
On the server itself
# Check if coturn is listening
sudo ss -tlnup | grep 3478In the app
A participant connected through the relay gets an amber swap_horiz icon next to their name, both on the stage and in the voice channel list. If nobody shows the icon, either everyone is connecting directly (the ideal case) or the relay did not start — check with monky logs.
Troubleshooting
| Symptom | Likely cause | Solution |
|---|---|---|
monky status shows ⚠ port blocked | Port 3478 closed in provider or Linux firewall | Follow the port opening steps above |
| coturn starts but nobody connects via relay | Missing external-ip in config (NAT-based VPS) | Update to v4.13.2+ — detection is automatic |
monky status shows coturn: unavailable | coturn is not installed | Follow manual installation |
| The TURN switch is greyed out in the app | Server is not Linux, or old version | Update the server; TURN only works on Linux |
| Call connects but with high latency | Normal for relay — media goes through the server | Consider a VPS closer to your members |
Disabling it
monky stop
monky config set turn false
monky startcoturn stays installed but is not started. No extra ports need to remain open.
How it works under the hood
Monky uses coturn, the reference TURN server. When the relay is enabled:
- Monky generates a random shared secret and persists it in the database
- On boot, it detects the VPS public IP (via ipify.org) and the local IP of the interface the default route uses (Docker bridges and VPN tunnels do not count)
- Generates
turnserver.confwith theexternal-ip=PUBLIC/PRIVATEdirective — without this, on NAT-based VPS (Oracle, AWS, etc.), coturn advertises the private IP and relay candidates become unreachable - Spawns coturn as a child process
- Verifies port 3478 is listening and, if possible, externally accessible
- At each client login, generates ephemeral credentials (TURN REST API) valid for 12 hours
- The client's WebRTC tries the direct route and only uses the relay if needed
What the relay refuses
The relay only forwards UDP (no-tcp-relay) and only towards the public internet: loopback, private networks (10/8, 172.16/12, 192.168/16), carrier-grade NAT (100.64/10, Tailscale's range) and the other reserved ranges are denied. That keeps anyone with credentials from using the relay to reach services of your internal network.
Exception behind NAT. When the server is behind NAT (the external-ip=PUBLIC/PRIVATE mapping above), Monky also writes allowed-peer-ip=PRIVATE. When both members of a call need the relay, media goes client → TURN → TURN → client, and coturn sees that second leg at the host's private address; without the exception those calls do not connect. The side effect: the relay can deliver UDP to any service of the host itself that listens on that private address (or on all addresses, 0.0.0.0). TCP stays closed, and the rest of the internal network stays denied. If the host runs a UDP service that should not be reachable by members, bind it to 127.0.0.1 or block it in the firewall. A host with the public IP on its own interface gets no mapping and no exception.
