Skip to content

Backend Transports

MEMOxiiii edited this page Sep 17, 2026 · 3 revisions

Backend Transports

Every backend server registered with Portal declares its own transport — raknet or nethernet — independently of every other server, and independently of the player listener's own transport (see NetherNet Transport). A single Portal instance can front a RakNet-only server, a NetherNet-only server, and a dual-transport server all at once; players transfer freely between all of them regardless of what they're each running.

This is set via the Transport field on the RegisterServer socket packet (see Socket Protocol) — there is no proxy-side config for it. The proxy learns each server's transport the moment it registers.

Address format depends on transport

Transport Address format Example
raknet "host:port" "127.0.0.1:19132"
nethernet Full URL of the server's own NetherNet signaling endpoint "http://127.0.0.1:19132"

Get this wrong and Portal rejects the registration immediately with a clear error (e.g. nethernet address must be the full URL of the server's signaling endpoint ..., got "127.0.0.1:19135") rather than accepting it and failing later inside a health check or a transfer — see Troubleshooting for the exact messages.

Two independent configs that must be kept in sync by hand

This is the single most common mistake when switching a backend to nethernet, so it gets its own section. There are two separate things, and neither reads the other:

  1. The backend server's own config decides whether it actually runs a NetherNet listener at all, and at what address.
  2. The client library's config decides what Address/Transport Portal is told to dial via RegisterServer.

If they disagree — say, the server only started a RakNet listener but the client library told Portal nethernet anyway — nothing errors at startup. Portal will simply try to reach an endpoint that isn't there, and health checks will silently mark the server unhealthy until it's fixed.

Both halves need to agree on the same address, in the format each side actually expects (a backend server's own listener config almost always wants a bare host:port, never a URL — only RegisterServer.Address, the value Portal itself is told, uses the full URL form for nethernet). For the concrete fields and a worked example on a specific platform, see that platform's own documentation — e.g. PortalDF's Wiki for Dragonfly, which walks through both halves side by side. This page only covers what Portal itself does with Address/Transport once it receives them.

udp_ports collisions across Portal and its own backends

If Portal itself is using network.transport: "nethernet" (the default) and a backend server also running NetherNet sits on the same physical host as Portal (common in local testing — everything on localhost), each of them needs its own distinct udp_ports value. Two independent NetherNet listeners on one machine trying to bind the same UDP port fails outright at startup for whichever one starts second — see Troubleshooting.

Health checks

A raknet server is checked with a real RakNet unconnected ping, same as always. A nethernet server has no unconnected-ping equivalent — signaling is a full HTTP round trip — so it's checked instead with a plain GET of its signaling endpoint's /v1/join route (the same route a Bedrock client probes to discover it), expecting a 200 response. Both share the same health_check.* settings in Configuration.

How NetherNet backend dialing actually works (implementation note)

This section is for anyone curious about the internals, or implementing a new client library that wants to support NetherNet as a backend transport — it isn't something you need to know to use the feature.

Dialing a NetherNet backend hits a real quirk in how gophertunnel validates login data: it expects a NetherNet connection's ClientData.ServerAddress field in the literal form "scheme://host:port:port" — the port repeated a second time — while the actual WebRTC signaling request has to go to the clean "scheme://host:port" URL. gophertunnel's dialer always sets ServerAddress to the exact same string used to dial, so no single address string can satisfy both requirements at once.

Portal resolves this with a small signaling proxy (session/nethernet_dial.go) that dials using the doubled-port form (so ClientData.ServerAddress validates correctly server-side) while transparently rewriting every signal it sends and receives back to the clean URL underneath, so the actual HTTP requests still reach the right place. This is entirely internal to Portal's dialing code — it has no effect on RegisterServer.Address, which stays the clean URL form described above.

Known limitation: health checks can't see past a NetherNet server's signaling process

A nethernet server's health check (GET /v1/join) is answered directly by the signaling endpoint itself, unconditionally, regardless of whether the actual Bedrock game server behind it is still running — this is how the underlying protocol defines that route, not something Portal controls. A backend whose game process has crashed or hung but whose signaling process is still up will keep reporting healthy. This is a real gap compared to a RakNet server's unconnected ping, which does reach the actual game listener. There's no code-level fix available from Portal's side; if this matters for your deployment, have the backend process itself monitor its own game loop and exit (taking the signaling endpoint down with it) rather than relying solely on Portal's health check to catch a hang.

Stability

Mixed-transport transfers (RakNet↔RakNet, RakNet↔NetherNet, NetherNet↔RakNet, NetherNet↔NetherNet) have been tested extensively across multiple backend servers with no failures after the initial connection settles — but that testing has so far been on a local network. Treat a first real-world, public-facing NetherNet backend deployment the same way you'd treat any other significant infrastructure change: roll it out to one server first and watch it for a while before switching everything over.

See also

Clone this wiki locally