Set up a Minecraft Bedrock server with NetherNet on Debian/Ubuntu (with HTTPS via nginx)

2026-09-23

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.

NetherNet splits a connection into two channels: a small HTTP signaling exchange over TCP, and the actual gameplay over UDP (encrypted WebRTC data channels). You must open ports for both.

Prerequisites

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.

1. Download and unpack BDS

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

2. Enable NetherNet in server.properties

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

3. Save the server identity key (and share it across your servers)

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.

Run more than one server? Copy the same 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.

4. Open the firewall (ufw)

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.

5. Run it under systemd

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

6. Verify locally

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.

7. (Optional) HTTPS via nginx + Let’s Encrypt

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.

7a. Bind BDS to loopback

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.

7b. Install nginx and get a certificate

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
The certificate must match the hostname players connect to. If your address is 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.

7c. nginx reverse proxy

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.

8. Check it works with iceFetch

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:

  1. Open the iceFetch tool.
  2. Enter your server’s Host (e.g. 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.
  3. Click Server info — you should see your server’s name, version, and player count. This confirms signaling is reachable externally.
  4. Click Fetch candidates — you should get one or more ICE candidates. These are the UDP endpoints your server advertises for gameplay; if they list your correct public address, your server-udp-ports mapping is right.
If Server info fails, signaling is not reachable (check the TCP port, NAT forward, or — for HTTPS — the certificate). If info works but Fetch candidates returns zero, the UDP pool is momentarily exhausted or the media ports are unreachable; widen server-udp-ports and check the UDP firewall.

Wrap-up

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.