This took me an entire day to work out, so I’m writing it up in case it saves someone else the same pain.
Short version: if you run Bedrock behind playit.gg and the 1.26.51 update locked everyone out, you need a TCP tunnel for the handshake and a UDP tunnel for gameplay, and the UDP port numbers on both sides have to be identical or the server silently refuses to listen. That last part is a bug, and it’s what cost me most of the day.
Everything below is confirmed working from outside the network, with no router port forwarding.
After updating, the console threw this:
Your current connection type is not set to NetherNet.
In this release, NetherNet is the only supported transport type.
Players will not be able to connect to your game without NetherNet.
1.26.51.1 dropped RakNet entirely. RakNet was UDP on 19132, which is what a standard playit “Minecraft Bedrock” tunnel forwards.
NetherNet works completely differently.
The handshake is HTTP over TCP on 19132. It’s also what answers the server status and MOTD query. GET /v1/join returns JSON.
Gameplay is UDP, negotiated separately per player, and it does not go through the tunnel at all. It goes straight to your box.
So a UDP-only Bedrock tunnel is now forwarding nothing useful. That’s why everyone is locked out.
The thing I misread, which wasted hours
The Mojang docs bundled with the server say “only the signaling traffic on server-port needs to be forwarded through the proxy; the gameplay traffic flows directly between the client and the server.”
I read that as the UDP sorting itself out via NAT traversal and needing nothing from me.
It does not mean that.
It means the gameplay UDP bypasses the proxy. It still has to reach your machine somehow.
The symptom if you get this wrong is nasty because it looks like it half works. I could join fine because I’m on the same LAN as the server box and my traffic never crossed the router.
Anyone outside got all the way to seeing the world name in the server list and then died with:
InitialConnection-83 codeword: Door
Transport: NetherNet:2193
Seeing the world name means the handshake worked. Dying at InitialConnection means the UDP leg never connected.
If that’s your error, your TCP side is fine and your UDP side is the problem.
The big gotcha: server-udp-ports only works 1:1
server-udp-ports is supposed to let you publish a NAT mapping in the form:
ip:external:internal
In 1.26.51.1, it only works if the external port numbers are IDENTICAL to the internal ones.
Anything else and the server boots, logs Server started., and then never opens its listener.
No error. Nothing in the log. It just sits there dead and you have no idea why.
| Value |
Result |
19140-19155 (bind only) |
works |
19140-19143:19140-19143 |
works |
<playit-ip>:61226-61229:61226-61229 |
works |
61226-61229:19140-19143 |
dead, no listener |
<playit-ip>:61226-61229:19140-19143 |
dead, no listener |
So whatever ports playit assigns you, the server has to use those exact same numbers locally.
Set your local port to match the tunnel, not the other way around.
Quick way to check if this is what’s biting you:
docker logs <container> | grep "Accepting clients"
If that line is missing, your server-udp-ports is bad.
Put it back to a plain range like 19140-19155 to get online again, then fix it properly.
Host networking is required
If you leave the container on the Docker bridge, the server gathers its network candidates from inside the container and advertises a useless private address.
It has to be:
network_mode: host
That changes what your tunnels point at.
TCP signaling binds *:19132, so 127.0.0.1 is fine for that tunnel.
UDP does not bind loopback, so that tunnel has to point at your actual LAN IP.
Also worth knowing: it binds UDP *:7551 regardless of what you configure. That’s a fixed NetherNet port, not something you set.
The working setup
services:
minecraft:
image: itzg/minecraft-bedrock-server:latest
restart: unless-stopped
network_mode: host
volumes:
- ./minecraft-data:/data
environment:
EULA: "TRUE"
GAMEMODE: "survival"
DIFFICULTY: "normal"
MAX_PLAYERS: "10"
VERSION: "LATEST"
TRANSPORT: "nethernet"
SERVER_UDP_PORTS: "<playit-udp-tunnel-ip>:61226-61229:61226-61229"
stdin_open: true
tty: true
Swap in whatever IP and ports your UDP tunnel got.
Heads up that TRANSPORT, SERVER_UDP_PORTS, and SERVER_IP only exist in itzg images built 2026-09-17 or later (PR #675). On an older image, they’re silently ignored and you’ll be very confused.
That build also switches the healthcheck to hit /v1/join when TRANSPORT=nethernet.
Two tunnels on the playit side:
A TCP tunnel, port count 1, pointing at:
127.0.0.1:19132
This is the handshake, and it’s the address and port your players actually type in. Put your custom domain on this one.
A UDP tunnel, port count 4, pointing at:
<your-lan-ip>:61226
Where 61226 is whatever port playit assigned that tunnel.
This is gameplay. Players never type this address, but the server has to advertise it via server-udp-ports, and the local port has to equal the assigned port because of the 1:1 bug above.
Your old Bedrock/UDP tunnel is dead weight. Delete it once the new pair is confirmed working.
You can check the handshake from anywhere with curl, which is much faster than loading Minecraft every time:
curl http://<your-tunnel-domain>:<tcp-port>/v1/join
You should get something like:
{"name":"...","protocol":2193,"version":"1.26.51","level":"...","players":0,"maxPlayers":10,"gameType":0}
If that returns JSON, your TCP side is done.
If it times out, your TCP tunnel is wrong.
If it returns JSON but people still can’t join, it’s the UDP side.
The server also writes its own minecraft-data/backup-pre-<version> when it upgrades.