Recent Minecraft Bedrock Dedicated Server (BDS) builds support NetherNet, a WebRTC-based transport that replaces the classic RakNet UDP protocol. This guide gives copy-paste commands for Debian 12 / Ubuntu 22.04+: install BDS, enable NetherNet, open the right ports, run it under systemd, optionally put HTTPS in front of the signaling with nginx, and finally verify the server is reachable using the iceFetch tool.
sudo apt update
sudo apt install -y unzip curl
You need a 64-bit Debian/Ubuntu host with a public IP (or a router you can port-forward). For the HTTPS section you also need a domain name pointing at the host.
Open the official Bedrock server download page, accept the terms, and copy the Linux download link (it is versioned, e.g. bedrock-server-1.26.x.zip). Then:
# paste the URL you copied from the download page
cd /tmp
curl -L -A "Mozilla/5.0" -o bedrock-server.zip "PASTE_THE_LINUX_ZIP_URL_HERE"
sudo mkdir -p /opt/bedrock
sudo unzip -o /tmp/bedrock-server.zip -d /opt/bedrock
# run it under a dedicated unprivileged user
sudo useradd -r -m -d /opt/bedrock -s /usr/sbin/nologin bedrock 2>/dev/null || true
sudo chown -R bedrock:bedrock /opt/bedrock
Edit /opt/bedrock/server.properties (for example sudo -u bedrock nano /opt/bedrock/server.properties) and set:
transport=nethernet
server-port=19132
# server-portv6 is ignored under nethernet — a dual-stack socket is opened on server-port.
# UDP client transport ports for the gameplay media.
# Format: [ip:]external[-external]:internal[-internal]
# Publish an externally reachable mapping (replace with YOUR public IP):
server-udp-ports=203.0.113.10:19132-19151:19132-19151
online-mode=true
allow-list=true
enable-lan-visibility=false
transport=nethernet makes BDS open a TCP HTTP signaling server on server-port, in addition to the UDP media sockets.server-udp-ports controls the UDP media ports and, with the ip: prefix, advertises a publicly reachable mapping. The ranges on each side of the colon must be the same length. You need at least one UDP port per concurrent player — open a range at least as large as max-players, and add headroom on top, because pending and half-open sessions also each hold a port until they time out. A range that is too small silently rejects new connections once it is full. The example above (19132–19151) gives 20 ports.Each server has a long-lived identity key. Its public key is what a client trusts — trust attaches to that key, not to the hostname, IP, or network id. On plain-HTTP signaling the client pins that key on first connection (trust-on-first-use), so the very first connection shows a one-time trust step.
The key is saved manually. On first startup, run this in the server console:
serveridentity save
This writes the running key to keys/server_identity_key.pem (you can check with serveridentity status, or remove it with serveridentity delete). Note that the running key does not change until the next start — restart the server after saving so it loads the persisted key.
keys/server_identity_key.pem to every server you own and restart them. They then present the same identity key, so clients treat the whole group as one identity: the first-time (HTTP-only) trust step happens once for the entire group instead of once per server. Keep this file private — anyone with it can impersonate your server group.This trust-on-first-use step applies to plain-HTTP signaling. If you front the signaling with HTTPS (see below), the TLS certificate provides the trust instead.
Two different things must be reachable — the TCP signaling port and the UDP media range:
sudo apt install -y ufw
sudo ufw allow 19132/tcp
sudo ufw allow 19132:19151/udp
sudo ufw enable
If the host is behind NAT, forward the same TCP port and UDP range to it on your router. A plain port-forward is enough — the client connects UDP directly to the candidates the server advertises; there is no separate relay.
Create /etc/systemd/system/bedrock.service:
sudo tee /etc/systemd/system/bedrock.service >/dev/null <<'UNIT'
[Unit]
Description=Minecraft Bedrock Dedicated Server
After=network-online.target
Wants=network-online.target
[Service]
User=bedrock
WorkingDirectory=/opt/bedrock
Environment=LD_LIBRARY_PATH=.
ExecStart=/opt/bedrock/bedrock_server
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
UNIT
sudo systemctl daemon-reload
sudo systemctl enable --now bedrock
sudo systemctl status bedrock --no-pager
journalctl -u bedrock -f # watch the log
The signaling endpoint answers a status request with JSON:
curl http://127.0.0.1:19132/v1/join
# {"name":"Dedicated Server","protocol":2193,"version":"1.26.x",
# "level":"...","players":0,"maxPlayers":10,"gameType":0}
If you see that JSON, signaling is up. At this point players can already connect over plain HTTP signaling. To serve the signaling over HTTPS instead, continue below.
Large networks serve the signaling endpoint over HTTPS. To do the same, bind BDS to loopback and let nginx terminate TLS on the public port.
In server.properties, restrict the signaling socket to localhost so nginx can own the public port, then restart:
server-ip=127.0.0.1
server-port=19132
sudo systemctl restart bedrock
(server-ip is honoured under nethernet.) The UDP media still uses the public mapping from server-udp-ports — nginx does not touch that.
sudo apt install -y nginx certbot
# HTTP-01 challenge needs port 80 reachable during issuance
sudo ufw allow 80/tcp
sudo certbot certonly --standalone -d mc.example.com
mc.example.com, the cert’s subject/SAN must be mc.example.com. A mismatch makes strict TLS clients and diagnostic tools reject the connection during the handshake.sudo tee /etc/nginx/sites-available/bedrock-signaling >/dev/null <<'CONF'
server {
listen 19132 ssl;
listen [::]:19132 ssl;
server_name mc.example.com;
ssl_certificate /etc/letsencrypt/live/mc.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mc.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:19132;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_read_timeout 30s;
}
}
CONF
sudo ln -sf /etc/nginx/sites-available/bedrock-signaling /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Keep the certificate fresh — certbot installs a renewal timer; reload nginx on renewal:
sudo certbot renew --deploy-hook "systemctl reload nginx" --dry-run
Verify over TLS:
curl https://mc.example.com:19132/v1/join
You should get the same JSON, now over HTTPS. Only the signaling is proxied; UDP media continues to flow directly to the ports from server-udp-ports.
A local curl only proves the server answers itself. To confirm it is reachable from the public internet — catching NAT, firewall, and certificate problems a local test cannot — use the iceFetch tool on this site, which reaches your server from Cloudflare’s edge:
mc.example.com) and Port (19132). Leave transport on auto — it uses plain HTTP, and falls back to TLS if your server is HTTPS-only.server-udp-ports mapping is right.server-udp-ports and check the UDP firewall.A NetherNet server is really two services on one address: an HTTP signaling endpoint (TCP) and WebRTC media (UDP). Get both reachable, run BDS under systemd, and optionally wrap the signaling in nginx + TLS for a clean HTTPS front — then confirm the whole thing from the outside with iceFetch. For what the signaling exchange actually looks like under the hood, see How iceFetch works.
ToolLearnArticlesPrivacyTerms · iceFetch is not affiliated with Mojang or Microsoft.