Skip to content

WebRTC Connections

Marc Pope edited this page Nov 1, 2026 · 1 revision

WebRTC connections

RustDesk 1.5 can connect two devices over WebRTC. It is another direct path, over UDP, and it gets through networks where the older TCP hole punch fails: phone hotspots, mobile data, carrier-grade NAT. When it works the session goes device to device instead of through the relay.

Since 1.17.0. The ID server only passes the offer, the answer and the network candidates between the two devices. It never sees session traffic.

Turning it on

  1. Upgrade to CortenDesk 1.17.0 (Docker image or bundled server), or to CortenDesk Server 1.2.0 if you run hbbs yourself.
  2. Web client: nothing to do. It uses WebRTC by default; check the Transport row in the session's Details panel.
  3. Desktop clients: on the device you connect from, open Settings → General → Other and tick Enable WebRTC P2P connection. The device you connect to needs nothing.
  4. Optional, recommended for mobile users: run a TURN server (below) and point clients at it: a strategy for desktop clients, Settings → Server → Web client ICE servers for the web client.

Every step falls back to what you have today: when WebRTC cannot connect, the session uses the hole punch or the relay as before.

Requirements

  • The server. The ID server bundled with CortenDesk 1.17.0, or CortenDesk Server 1.2.0 if you run hbbs yourself. Stock rustdesk-server does not pass WebRTC offers on.
  • RustDesk 1.5.0 or later on both devices.
  • WebRTC turned on where the connection starts. On the connecting device: Settings → General → Other → Enable WebRTC P2P connection. The device being controlled needs nothing. This is a local client setting, so a strategy cannot turn it on.
  • Different networks, for desktop clients. Two devices behind the same public IP connect over the LAN, and WebRTC is not tried. The web client has no such shortcut and uses WebRTC on a LAN too.

The web client

The web client uses WebRTC too, which matters more there: a browser cannot punch holes, so without WebRTC every web session goes through the relay.

  • It is on by default. Settings → Server → Web client uses WebRTC turns it off.
  • Web client ICE servers, beside it, sets the browser's STUN and TURN servers, in the same format as the strategy option. Empty means public STUN servers. Every console user who opens the web client receives the TURN password.
  • The browser and the device learn each other's IP addresses, as with the desktop client.
  • Give the web client a TURN server if your users work from mobile data. Carrier NAT on phone networks and hotspots blocks every direct path, and a browser cannot use VPN interfaces (Tailscale, WireGuard) the way the desktop client can. Without TURN such a session simply uses the relay.
  • The session's Details panel shows Transport: WebRTC, direct, WebRTC, through TURN or Relay.
  • Add ?debug=1 to the web client's address to log candidates, ICE state and the selected pair to the browser console.

How a connection is chosen

The connecting device tries the TCP hole punch and WebRTC at the same time and keeps whichever connects first. If neither does, it falls back to the relay, as before. So on a network where TCP punching works you will often see TCP win, and WebRTC shows up where it fails.

A relay that connects first waits 2.5 seconds for WebRTC before it is used, and so does WebRTC through TURN. A direct WebRTC connection wins as soon as it opens. The web client follows the same rule, with the relay in place of the hole punch.

Checking that it is used

  • Web client: the session's Details panel (the Session button on the toolbar) shows Transport: WebRTC, direct, WebRTC, through TURN or Relay.

  • Desktop client: hover over the session's tab; the tooltip names the connection type.

  • On the server: the ID server logs each exchange.

    WebRTC offer from 203.0.113.20:51234 to 123456789
    WebRTC answer from 123456789 to 203.0.113.20:51234
    

    With the bundled server: docker logs <container> 2>&1 | grep WebRTC.

STUN and TURN servers

WebRTC uses STUN to learn each device's public address, and TURN, if you have one, as a relayed path when no direct one forms. Out of the box the client uses four public STUN servers (Cloudflare, Google, antisip and Nextcloud) and no TURN.

To use your own, set WebRTC ICE servers (ice-servers) in a strategy, under Network:

stun:turn.example.com:3478, turn://rustdesk:secret@turn.example.com:3478
Form Use
stun:host[:port], stuns:host[:port] STUN
turn://user:password@host[:port], turns://… TURN. Credentials are required, and only this form carries them.
  • The port defaults to 3478. Put an IPv6 address in brackets: stun:[2001:db8::1]:3478.
  • Percent-encode anything in the user name or password other than letters, digits and -._~. A comma must be %2C, or it splits the list.
  • A STUN entry replaces the public servers. TURN alone keeps them. List your own STUN server too if devices must not contact the public ones.
  • The client silently skips an entry it cannot use, so the console refuses the whole list and names the bad entry.
  • It takes effect at the next connection. Set it on both sides: each device gathers its own candidates.
  • Every device in the strategy receives the TURN password, and anyone who can view Strategies can read it. Use an account that exists only for this.

Running your own TURN server

Optional. Without TURN, a connection that cannot go direct uses the RustDesk relay (hbbr), so nothing breaks. TURN adds a relayed WebRTC path, and your own STUN server means devices never contact the public ones.

coturn provides both. Run it on a host with a public IP. turnserver.conf:

listening-port=3478
realm=turn.example.com
fingerprint
no-tls
lt-cred-mech
user=rustdesk:change-me
min-port=49160
max-port=49200
no-multicast-peers
# Behind NAT (most clouds): external-ip=<public IP>/<private IP>
# Keep the relay from reaching your internal network.
denied-peer-ip=0.0.0.0-0.255.255.255
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=100.64.0.0-100.127.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=169.254.0.0-169.254.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=::1
denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff

Compose:

services:
  coturn:
    image: coturn/coturn:4.18
    network_mode: host
    restart: unless-stopped
    volumes:
      - ./turnserver.conf:/etc/coturn/turnserver.conf:ro

Open 3478 UDP and 49160–49200 UDP on the firewall. Each relayed session uses ports from that range, so widen it for a large fleet.

The denied-peer-ip lines matter. A TURN server relays to any address it is asked to, and every device in the strategy holds the password. Without them, anyone with a device can use it to reach machines on the TURN server's own network.

coturn logs Bad configuration format for options it no longer knows. Check docker logs after a change.

Troubleshooting

Symptom Cause
No WebRTC offer line in the server log WebRTC is off on the connecting device, the client is older than 1.5.0, both devices share a public IP, or the server predates WebRTC support
Offer and answer logged, but no Transport row TCP connected first, which is normal. Or UDP is blocked on one side: a TURN server gives it a relayed path
Works on some networks, never on others Strict NAT on both sides. Add a TURN server
Client log: failed to resolve stun host … No available ipv6 Harmless. The client also tries IPv6 and the machine has none
Web client on mobile data always shows Relay Expected without TURN: carrier NAT drops every direct check. With ?debug=1 the console lists each candidate pair with req N/0. Set Web client ICE servers to include a TURN server
Desktop client connects over WebRTC where the browser cannot The desktop client also uses VPN interfaces such as Tailscale; a browser uses only the default network

Related

Clone this wiki locally