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.

UFW rules reference.

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.yml while the proxy is running: BungeeCord overwrites your file when it shuts down. Stop the proxy before editing, or run /bungee to 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: true only matters if backends run Forge with FML. For vanilla / Paper / Fabric, leave it false.
  • network_compression_threshold on 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-mode true, 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.


Related posts