BungeeCord / Waterfall Setup — Running a Minecraft Server Network
BungeeCord network on one Linux box: proxy, Paper backends on localhost, IP forwarding, systemd template, DNS. Plus why Waterfall is EOL.
A Minecraft "network" is just one proxy on port 25565 and several normal servers behind it that nobody can reach directly. Players connect to the proxy, land in the lobby, /server survival moves them over without a relog. This post builds that end-to-end with BungeeCord: proxy, two Paper backends, IP forwarding, systemd, DNS.
TL;DR:
sudo adduser --system --group --home /opt/mc mc
sudo -u mc mkdir -p /opt/mc/{proxy,lobby,survival}
sudo -u mc wget -O /opt/mc/proxy/BungeeCord.jar \
https://ci.md-5.net/job/BungeeCord/lastSuccessfulBuild/artifact/bootstrap/target/BungeeCord.jar
# backends: server-ip=127.0.0.1, online-mode=false, spigot.yml bungeecord: true
# proxy: ip_forward: true, online_mode: true
sudo ufw allow 25565/tcp
If you're starting a new network today, though, read the next section first.
The honest part: Waterfall is dead, and Velocity is the better pick
Waterfall — PaperMC's BungeeCord fork — has reached end of life: PaperMC announced it in March 2024 and points everyone to Velocity. Existing Waterfall installs keep running and builds still show up occasionally, but there's no promise of updates for new Minecraft versions or of timely security fixes. If you're on Waterfall right now, that's fine for this week — just don't build anything new on it, and plan the switch.
That leaves two options:
- BungeeCord — the original, still maintained by md_5 / SpigotMC, still gets updates for new Minecraft versions. Legacy IP forwarding only (see below).
- Velocity — PaperMC's modern proxy. Signed "modern" forwarding, active development, different config and plugin API.
For a fresh network I'd go Velocity, and I have a full walkthrough: Velocity from scratch. This post is for people who need BungeeCord — an existing plugin set that only exists for Bungee, very old client versions, or a network you inherited. If you're unsure, the Waterfall vs BungeeCord comparison covers the lineage, and the migration guide shows what moving to Velocity costs.
Everything below about backends, ports and firewalls applies to Waterfall too — same config, same jar slot.
Layout
/opt/mc/proxy BungeeCord 0.0.0.0:25565 (public)
/opt/mc/lobby Paper 127.0.0.1:25566 (local only)
/opt/mc/survival Paper 127.0.0.1:25567 (local only)
One system user, one directory per server. Backends on the same box as the proxy, bound to localhost. That last part is the whole security model, so I'll repeat it a few times.
Java
BungeeCord itself is happy on a recent LTS Java, and your Paper backends on current Minecraft need Java 21, so install 21 once and use it for everything:
sudo apt install -y temurin-21-jre
java -version
Repo setup for Temurin is in the Minecraft Java server post.
The backends
Set up each backend like a normal Paper server — download Paper, run once, accept the EULA. The single-server walkthrough is Minecraft Java server on Linux. Then change three files per backend.
server.properties:
server-ip=127.0.0.1
server-port=25566
online-mode=false
Survival gets 25567. online-mode=false because the proxy authenticates players; the backend trusts what the proxy forwards.
spigot.yml:
settings:
bungeecord: true
This tells the backend to accept the forwarded IP and UUID from the proxy's handshake. Without it, every player shows up as 127.0.0.1 with an offline-mode UUID — bans break, permissions keyed by UUID break.
config/paper-global.yml:
proxies:
bungee-cord:
online-mode: true
Tells Paper that the proxy is in online mode, so it uses the real Mojang UUIDs.
Gotcha: a public backend with online-mode=false
Legacy BungeeCord forwarding is pure trust. The backend believes whatever name and UUID arrive in the handshake. If a backend port is reachable from the internet, anyone can connect directly and claim to be you — including your op account.
server-ip=127.0.0.1 is the first line of defense. The firewall is the second:
sudo ufw deny 25566:25600/tcp
ss -tlnp | grep java
ss should show the backends on 127.0.0.1, only the proxy on 0.0.0.0. If backends must live on another machine, firewall them so only the proxy's IP can connect, and add BungeeGuard (plugin on proxy + backends) so the handshake carries a secret token.
The proxy
cd /opt/mc/proxy
sudo -u mc java -Xms512M -Xmx512M -jar BungeeCord.jar
First start writes config.yml, then type end. Minimum edits:
listeners:
- host: 0.0.0.0:25565
motd: '&6My Network'
max_players: 100
priorities:
- lobby
forced_hosts:
survival.example.com: survival
ping_passthrough: false
tab_list: GLOBAL_PING
servers:
lobby:
address: 127.0.0.1:25566
restricted: false
survival:
address: 127.0.0.1:25567
restricted: false
ip_forward: true
online_mode: true
network_compression_threshold: 256
The lines that matter:
ip_forward: true— the other half ofbungeecord: trueon the backends. Both or neither.online_mode: true— the proxy is where Mojang authentication happens.priorities— where players land on join, with fallback.- Delete the default
serversentry if it's still in there pointing atlocalhost:25565— that's the proxy's own port.
Every key in that file, including the undocumented ones: BungeeCord config.yml explained.
systemd: one template for all servers
Instead of three unit files, one template. /etc/systemd/system/mc@.service:
[Unit]
Description=Minecraft %i
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=mc
Group=mc
WorkingDirectory=/opt/mc/%i
EnvironmentFile=/opt/mc/%i/jvm.env
ExecStart=/usr/lib/jvm/temurin-21-jre-amd64/bin/java $JAVA_OPTS -jar server.jar
Restart=on-failure
RestartSec=15
TimeoutStopSec=120
SuccessExitStatus=0 143
[Install]
WantedBy=multi-user.target
Each directory gets a jvm.env and a server.jar symlink:
# /opt/mc/lobby/jvm.env
JAVA_OPTS=-Xms2G -Xmx2G -XX:+UseG1GC -XX:+AlwaysPreTouch
# /opt/mc/proxy/jvm.env
JAVA_OPTS=-Xms512M -Xmx512M -XX:+UseG1GC
sudo -u mc ln -s BungeeCord.jar /opt/mc/proxy/server.jar
sudo -u mc ln -s paper.jar /opt/mc/lobby/server.jar
sudo -u mc ln -s paper.jar /opt/mc/survival/server.jar
sudo systemctl daemon-reload
sudo systemctl enable --now mc@lobby mc@survival mc@proxy
journalctl -u mc@proxy -f
Backends get the full Aikar flag set from the single-server post; I shortened them here to keep it readable. The proxy needs very little memory — it shuffles packets and doesn't load worlds. %i is replaced by whatever comes after the @, so mc@lobby runs in /opt/mc/lobby.
Gotcha: BungeeCord rewrites config.yml
The proxy writes its config back to disk on shutdown. Edit config.yml while it's running, then restart, and your edits are gone. Stop first, edit, start:
sudo systemctl stop mc@proxy
sudo -u mc nano /opt/mc/proxy/config.yml
sudo systemctl start mc@proxy
DNS
Point an A record (plus AAAA if you bind on [::]) at the proxy IP:
play.example.com. A 203.0.113.10
survival.example.com. A 203.0.113.10
Same IP for both — forced_hosts in the config routes survival.example.com straight to the survival backend, everything else to the lobby. If you want players to connect on a non-default port without typing it, an SRV record does it:
_minecraft._tcp.play.example.com. SRV 0 5 25570 play.example.com.
Players type play.example.com, the client looks up the SRV and connects to 25570. Careful combining SRV with forced hosts: they match on the hostname the client puts into the handshake, and with SRV in the mix that isn't always the name the player typed. Test it before you announce the address.
Permissions across the network
Bungee's built-in groups: / permissions: block is fine for two admins. Anything more, install LuckPerms on the proxy and every backend, pointed at one shared MySQL/MariaDB — then a rank change applies network-wide. Walkthrough: LuckPerms on BungeeCord and Velocity.
Testing it
- Connect to
play.example.com. You land in the lobby. /server survival— switches without a relog.- On the survival console: check the player's UUID. It must be your real Mojang UUID. An offline-mode UUID means
ip_forwardorbungeecord: trueis missing somewhere. - From outside the box, try connecting to
<public-ip>:25566. It must fail. If it connects, stop everything and fix the firewall before anything else.
Gotcha: "If you wish to use IP forwarding, please enable it in your BungeeCord config as well!"
That kick message means the backend has bungeecord: true but the proxy has ip_forward: false — or the player connected to the backend directly, bypassing the proxy. Fix the proxy config, or close the backend port you forgot.
Where to go from here
Running this on BungeeCord works and will keep working as long as md_5 keeps updating it. But the security model is "trust the handshake, firewall everything", and the plugin ecosystem is shifting to Velocity. When you have an evening, do the migration to Velocity — the backends from this post stay the same apart from switching to modern forwarding.