Using ExpressTURN with Godot 4

Multiplayer in Godot is WebRTC under the hood. Add TURN, players connect.

Single peer connection

Godot's WebRTC API is in the official webrtc module (compiled into the engine in 4.x).

extends Node

var peer := WebRTCPeerConnection.new()

func _ready() -> void:
    peer.initialize({
        "iceServers": [
            { "urls": ["stun:stun.expressturn.com:3478"] },
            {
                "urls": [
                    "turn:relay1.expressturn.com:3478?transport=udp",
                    "turn:relay1.expressturn.com:3478?transport=tcp",
                    "turns:relay1.expressturn.com:443?transport=tcp"
                ],
                "username": "YOUR_EXPRESSTURN_USERNAME",
                "credential": "YOUR_EXPRESSTURN_PASSWORD"
            }
        ]
    })
    var ch := peer.create_data_channel("game", { "id": 1, "negotiated": true })
    peer.session_description_created.connect(_on_offer)
    peer.create_offer()

func _on_offer(type: String, sdp: String) -> void:
    peer.set_local_description(type, sdp)
    # send sdp to your signaling server

Mesh multiplayer with WebRTCMultiplayerPeer

var multiplayer_peer := WebRTCMultiplayerPeer.new()
multiplayer_peer.create_mesh(1)  # 1 = host
multiplayer.multiplayer_peer = multiplayer_peer

# When a player joins, create a connection for them and add it
func add_player(peer_id: int) -> void:
    var conn := WebRTCPeerConnection.new()
    conn.initialize({ "iceServers": ICE_SERVERS })  # same array as above
    multiplayer_peer.add_peer(conn, peer_id)
    # exchange offer/answer/ICE candidates with that peer's signaling channel

HTML5 export — Web games

Godot games exported to HTML5 use the browser's native WebRTC. Same iceServers configuration applies — Godot translates the dictionary into RTCConfiguration. The TURN-over-TLS path on port 443 is what saves your players on locked-down networks.

Common pitfalls

  • "WebRTC module not enabled": In older Godot 4.x export templates the module is optional. Use the standard templates from godotengine.org or rebuild with module_webrtc_enabled=yes.
  • Forgetting to call peer.poll(): WebRTCPeerConnection in Godot needs poll() called every frame. Easy to miss; connection appears stuck.
  • Hard-coded credentials: Anyone can decompile your client. For published games, mint per-session credentials server-side via shared-secret HMAC.

Related: TURN for multiplayer games · Pion (Go server) recipe

Done.

Get Free TURN Credentials