Ran a BungeeCord network for a couple of years. The config file is short — maybe 60 lines — but half the keys aren't documented anywhere obvious, and the defaults are wrong for most setups.
TL;DR — minimum viable config:
listeners:
- host: 0.0.0.0:25565
proxy_protocol: false
forced_hosts:
pvp.example.com: pvp
priorities:
- lobby
query_enabled: false
servers:
lobby:
address: 127.0.0.1:25566
restricted: false
pvp:
address: 127.0.0.1:25567
restricted: false
ip_forward: true
online_mode: true
Set ip_forward: true, set every backend server's online-mode=false in their own server.properties, and you're 80% there. Rest of this post is what the other 20% does.
If you haven't set up the proxy itself yet, start with the BungeeCord / Waterfall network setup guide — this post assumes the proxy is already running.
listeners
You can have multiple listeners on different ports. 99% of setups have exactly one. The interesting fields:
listeners:
- host: 0.0.0.0:25565
query_port: 25577
motd: '&6Welcome to the network'
tab_list: GLOBAL_PING
max_players: 200
proxy_protocol: false
forced_hosts:
pvp.example.com: pvp
creative.example.com: creative
priorities:
- lobby
- lobby2
bind_local_address: true
ping_passthrough: false
query_enabled: false
force_default_server: false
host — bind address. 0.0.0.0:25565 for IPv4, [::]:25565 for dual-stack. If you're behind a reverse proxy or firewall, you can bind to 127.0.0.1 and only expose via the proxy.
forced_hosts — domain → backend server mapping. Player connecting to pvp.example.com:25565 lands on pvp directly, skipping the lobby. Set the DNS A record to the proxy IP. Free traffic-routing.
priorities — fallback chain when a player joins. First entry the proxy can reach gets the connection. List your lobby servers here, in order. If the first one's down, second takes over. Player never sees the failure.
tab_list: GLOBAL_PING — tab list shows everyone on the network with real ping. GLOBAL (no ping) is faster but less useful. SERVER shows only the current backend.
ping_passthrough: false — when false, server list ping shows the proxy's MOTD and player count (sum of all backends). When true, it forwards the ping to whatever's first in priorities. I leave it false — I want the network MOTD, not the lobby's.
force_default_server: true — every login goes through the priority chain, even reconnects. Default false means players reconnect to wherever they were when they disconnected. Set true if you want everyone to always start at the lobby.
proxy_protocol: true — only enable if you're behind HAProxy / nginx with PROXY protocol. Otherwise BungeeCord rejects connections because the handshake bytes are wrong.
servers
servers:
lobby:
address: 127.0.0.1:25566
motd: '&6Lobby'
restricted: false
pvp:
address: 127.0.0.1:25567
motd: '&cPvP arena'
restricted: true
address — IP:port of the backend. If the backends are on the same box (they should be), use 127.0.0.1 — saves a network hop and avoids exposing the backend ports publicly.
restricted: true — players need the bungeecord.server.<name> permission to use /server pvp. Useful for staff-only servers.
The server name (the YAML key, e.g. lobby) is what /server <name> uses and what forced_hosts references. Keep it short.
ip_forward
ip_forward: true
This is the one that catches everyone. When true, BungeeCord rewrites the handshake to include the player's real IP and UUID, and the backend trusts it. When false, every player on every backend appears with the proxy's IP — bans don't work, plugins like CoreProtect log everything as 127.0.0.1.
But: ip_forward: true only works if the backend has online-mode=false in server.properties AND nothing else can connect to that backend's port. Bind backend ports to 127.0.0.1 only. UFW rule:
sudo ufw deny 25566:25600/tcp
If a backend port is publicly reachable AND online-mode=false, anyone on the internet can connect as any username. Free op for whoever finds it.
online_mode
online_mode: true
This is the proxy's setting. true means the proxy authenticates players against Mojang. Should always be true for a public server. The backends are online-mode=false because they trust the proxy — that's the whole IP-forwarding contract.
groups and permissions
groups:
md_5:
- admin
permissions:
default:
- bungeecord.command.server
- bungeecord.command.list
admin:
- bungeecord.command.alert
- bungeecord.command.end
- bungeecord.command.find
- bungeecord.command.ip
- bungeecord.command.reload
- bungeecord.command.send
Built-in permissions system. default applies to everyone, named groups (e.g. admin) apply to listed players. groups maps player name → group names.
For anything bigger than 5 admins, install LuckPerms-Bungee instead. The built-in system has no inheritance, no wildcards, and reloads only via /bungee — fine for a quick test, painful at scale.
connection_throttle
connection_throttle: 4000
connection_throttle_limit: 3
Per-IP rate limit. connection_throttle: 4000 = 4 seconds. Same IP making more than connection_throttle_limit connections in that window gets a temporary block. Default 3 is fine for a small server. Bigger networks with NAT'd schools / dorms need 10+ or you'll lock out half the players when the bell rings.
stats
stats: a-uuid-string
Anonymous metrics for the BungeeCord project. Auto-generated. You can leave it. Setting disable_entity_metrics: true only matters for old plugins, ignore it.
remote_ping_cache
remote_ping_cache: -1
How long (ms) BungeeCord caches backend ping responses. -1 = forever (until backend reconnects). 5000 = 5 seconds. Useful if your network gets hammered by ping bots — caching means backends don't see the load. Default is -1, which is fine.
Full annotated config I actually run
player_limit: -1
ip_forward: true
permissions:
default:
- bungeecord.command.server
- bungeecord.command.list
admin:
- bungeecord.command.alert
- bungeecord.command.end
- bungeecord.command.find
- bungeecord.command.ip
- bungeecord.command.reload
- bungeecord.command.send
- bungeecord.command.kick
groups:
Lukas:
- admin
timeout: 30000
connection_throttle: 4000
connection_throttle_limit: 3
servers:
lobby:
motd: '&6Welcome'
address: 127.0.0.1:25566
restricted: false
survival:
motd: '&aSurvival'
address: 127.0.0.1:25567
restricted: false
creative:
motd: '&bCreative'
address: 127.0.0.1:25568
restricted: false
listeners:
- host: 0.0.0.0:25565
motd: '&6&lThe Network &7- &fpick a world'
max_players: 100
priorities:
- lobby
forced_hosts:
survival.example.com: survival
creative.example.com: creative
query_enabled: false
ping_passthrough: false
proxy_protocol: false
tab_list: GLOBAL_PING
disabled_commands:
- disabledcommandhere
network_compression_threshold: 256
online_mode: true
log_commands: false
forge_support: false
Gotchas
- Editing
config.ymlwhile the proxy is running: BungeeCord overwrites your file when it shuts down. Stop the proxy before editing, or run/bungeeto reload — but reload doesn't pick up listener changes, only servers and permissions. For listener changes, full restart. - YAML indentation is 2 spaces. Tabs break the parser silently — proxy starts with a default config and you wonder where your servers went.
forge_support: trueonly matters if backends run Forge with FML. For vanilla / Paper / Fabric, leave itfalse.network_compression_thresholdon the proxy and the backends should match. Mismatch = slow connections, not broken ones, so you might not notice for weeks.- The proxy doesn't validate that backends have
online-mode=false. It just rewrites the handshake. If a backend is online-modetrue, players get kicked with "bad login" and the cause isn't obvious.
Picking the right proxy
Whether you actually want BungeeCord or its fork Waterfall is a different question — see Waterfall vs BungeeCord. The config syntax is identical between them, so this post applies to both.
Backing up
config.yml lives next to the JAR. Back up the whole proxy directory nightly:
rsync -a --delete /opt/bungeecord/ /backup/bungeecord/$(date +%F)/
Pattern from rsync backups on Linux. The config is small but losing it means rebuilding the network mapping from memory, which is no fun on a Friday night.