-
Notifications
You must be signed in to change notification settings - Fork 0
NetherNet Transport
NetherNet is Bedrock's official WebRTC-based transport, the same mechanism Bedrock Dedicated Server exposes when transport=nethernet is set in server.properties. It's Portal's default and official player-facing transport (network.transport: "nethernet") — RakNet ("raknet") is still available as a straight swap for anyone who needs it, see Configuration.
This page covers what actually changes under the hood, and — more importantly — what you need to get right to run it on anything beyond localhost. If something isn't connecting, check Troubleshooting after reading the relevant section here.
Unlike RakNet (a single UDP socket), NetherNet splits a connection into two independent channels:
-
Signaling — a plain HTTP(S) endpoint, served on
network.addressover TCP. This is how a client and Portal exchange WebRTC connection details (SDP offer/answer) to set up the real connection. It's what responds to a client'sGET /v1/joinprobe. -
Media — the actual WebRTC data channel (ICE → DTLS → SCTP) once signaling completes, over UDP, on the port(s) configured by
network.nethernet.udp_ports.
Signaling succeeding (you'll see nethernet: GET /v1/join / POST /v1/join/... in the debug log) does not mean the connection is up — the media channel has to establish separately afterward, and that's where firewall/NAT problems show up, as accepted connection never following the signaling lines, or start ICE: context deadline exceeded in errorlog-level output.
When a player manually adds a server by IP and port (or types one to join), a Bedrock client that supports NetherNet tries, in order, stopping at the first that responds: https://host:port, https://host (443), http://host:port, http://host (80). An explicit port (which is the normal case — Portal's default network.address is :19132) collapses this to just the https/http variants on that port. If none respond, the client falls back to RakNet on the same address — which only works if a RakNet listener is actually there too; Portal's own player listener is one or the other, not both simultaneously (see Configuration's network.transport).
All of this lives under network.nethernet in config.json — see Configuration for the full file.
| Key | Purpose | Default |
|---|---|---|
tls.cert_file / tls.key_file
|
PEM cert/key to serve signaling over HTTPS. Empty serves plain HTTP — clients still find it (see above), but HTTPS is recommended for anything reachable from the public internet |
"" / ""
|
ice_servers |
STUN/TURN servers offered for WebRTC NAT traversal, as [{"urls": [...], "username": "", "password": ""}]
|
[] (none) |
udp_ports |
UDP port, or "min-max" range, for the media channel |
"19133" |
A single port (the default, "19133") is shared by every player connection through one UDP mux, and is the easiest to forward through a firewall or router. A range spreads connections across more ports but can run out under heavy load; leaving it empty lets the OS pick an ephemeral port per connection, which almost no firewall allows unsolicited inbound traffic to by default — don't do this anywhere but a fully open network.
This port must be unique on the host it runs on. It cannot be shared with:
- RakNet's own port, if anything on the same machine uses RakNet (different protocol, but Portal's own default of
19133was deliberately chosen distinct from RakNet's usual19132). - Portal's own
udp_portsvalue, if a NetherNet-transport backend server also happens to run on the same physical host as Portal itself (a very common setup for local development/testing). Two independent NetherNet listeners both trying to bind the same UDP port fails outright — see Troubleshooting for the exact error and fix.
If you're running Portal plus one or more NetherNet-transport backend servers all on one machine, give each of them (Portal included) its own distinct udp_ports value.
WebRTC needs a way for two peers to find a working network path. On the exact same machine (e.g. testing a client and Portal both on localhost), this works with no configuration because ICE can use direct "host" candidates. The moment a real second device is involved — even one on the same Wi-Fi network — that's no longer guaranteed, and without any STUN/TURN servers configured, ICE negotiation can simply time out (start ICE: context deadline exceeded in the log) even though signaling worked perfectly.
A public STUN server is free and enough to fix same-network and most NAT cases:
"ice_servers": [
{ "urls": ["stun:stun.l.google.com:19302"] }
]For players behind restrictive/symmetric NATs or across the open internet where a STUN-assisted direct path still can't be found, you need a TURN server (relay) as well — STUN alone can't help there.
Two things need to be reachable from wherever your players actually are:
-
TCP on
network.address's port (default19132) — signaling. -
UDP on
network.nethernet.udp_ports(default19133) — media.
On Windows, the TCP side is often allowed automatically (or via the first-run "Allow this app" prompt), but the UDP media port commonly needs an explicit inbound rule:
New-NetFirewallRule -DisplayName "Portal NetherNet Media" -Direction Inbound -Protocol UDP -LocalPort 19133 -Action Allow(Adjust the port to whatever network.nethernet.udp_ports is actually set to, and run this as Administrator.) If players outside your local network need to reach the proxy, both ports also need forwarding on your router, same as they would for RakNet.
Portal generates a temporary P-384 ECDSA key to identify itself to connecting clients every time it starts, logging:
WARN generating a new private key for this listener. a TOFU (Trust on First Use) prompt may be surfaced to players on first join
This is expected and harmless — it just means returning players may occasionally see a "trust this server" prompt again after a proxy restart, since the identity looks different each time. There is currently no config option to persist this key across restarts; if that matters for your deployment, open an issue.
The "client checks NetherNet before falling back to RakNet" behavior described above depends on the connecting player's Bedrock client actually supporting it. This is a relatively new client-side capability — most current clients do, but if a specific player genuinely can't connect while everyone else can, an outdated client is worth ruling out before assuming a server-side problem. See Troubleshooting for how to tell the two apart from the log.
- Backend Transports — the same NetherNet/RakNet choice, applied per backend server instead of the player listener
- Troubleshooting — exact error messages and fixes for everything on this page
Getting started
Integrating a backend
Embedding Portal