If you're starting a new Minecraft network in 2026, skip BungeeCord and Waterfall — go straight to Velocity. PaperMC's proxy is the active project, the docs are current, and the IP forwarding is cryptographically signed instead of trust-based.
This is the from-zero install. If you already have BungeeCord/Waterfall and want to migrate, see the migration guide instead.
TL;DR:
sudo adduser --system --group --home /opt/velocity velocity
cd /opt/velocity
sudo -u velocity wget -O velocity.jar \
https://api.papermc.io/v2/projects/velocity/versions/latest/builds/latest/downloads/velocity.jar
sudo -u velocity java -jar velocity.jar # creates configs, then Ctrl-C
Then edit velocity.toml, point at your Paper backends, copy the forwarding.secret to each backend's paper-global.yml, systemd-enable, done.
Prerequisites
- Linux box with Java 17+ (I run Temurin 21 — see docker-install on Ubuntu for similar repo-based install). For Velocity:
sudo apt install temurin-21-jdk - Paper backends already running on the same box or LAN-reachable. Bare vanilla / Spigot won't do modern forwarding well.
- A port to expose publicly (default 25565).
Install
sudo adduser --system --group --home /opt/velocity velocity
cd /opt/velocity
sudo -u velocity wget -O velocity.jar \
https://api.papermc.io/v2/projects/velocity/versions/latest/builds/latest/downloads/velocity.jar
sudo -u velocity java -jar velocity.jar
First run generates:
velocity.toml— main configforwarding.secret— random secret for modern IP forwardingplugins/— emptylogs/— empty
Ctrl-C. We'll write the config from scratch.
velocity.toml — minimum sane config
config-version = "2.7"
bind = "0.0.0.0:25565"
motd = "&6&lMy Network &7- &fpick a world"
show-max-players = 100
online-mode = true
prevent-client-proxy-connections = false
player-info-forwarding-mode = "modern"
forwarding-secret-file = "forwarding.secret"
announce-forge = false
announce-proxy-commands = true
log-command-executions = false
log-player-connections = true
[servers]
lobby = "127.0.0.1:25566"
survival = "127.0.0.1:25567"
try = ["lobby"]
[forced-hosts]
"survival.example.com" = ["survival"]
[advanced]
compression-threshold = 256
compression-level = -1
login-ratelimit = 3000
connection-timeout = 5000
read-timeout = 30000
haproxy-protocol = false
tcp-fast-open = false
bungee-plugin-message-channel = true
show-ping-requests = false
failover-on-unexpected-server-disconnect = true
kick-existing-players = false
ping-passthrough = "DISABLED"
sample-players-in-ping = false
[query]
enabled = false
port = 25565
map = "Velocity"
show-plugins = false
The lines that actually matter:
bind— public-facing port. The backends should bind to127.0.0.1only and never accept external connections.online-mode = true— proxy authenticates against Mojang. Always true unless you specifically want a cracked server.player-info-forwarding-mode = "modern"— players' real UUID + IP get cryptographically forwarded to the backend. The backend verifies the signature.[servers]— name → backend address. Thetrylist is the fallback chain on join (lobby first, lobby2 if lobby is down, etc.).[forced-hosts]— domain-based routing.survival.example.comskips the lobby and lands onsurvival.
For a deeper reference of options and equivalents to BungeeCord syntax, see my BungeeCord config.yml deep-dive — the concepts map closely.
Backends — modern forwarding
Every Paper backend (1.13+) needs to know the forwarding secret. Edit config/paper-global.yml:
proxies:
velocity:
enabled: true
online-mode: true
secret: "<contents-of-/opt/velocity/forwarding.secret>"
Get the secret:
cat /opt/velocity/forwarding.secret
Paste literally — no extra spaces, no trailing newline.
Each backend's server.properties needs online-mode=false. Yes, false. The proxy is the authentication tier; backends trust the proxy.
But: if a backend's port is reachable from the internet AND online-mode=false, anyone can connect as any username. Bind backends to 127.0.0.1:
server-ip=127.0.0.1
server-port=25566
And the UFW rule to be sure:
sudo ufw deny 25566:25600/tcp
Restart each backend after editing.
Systemd unit
/etc/systemd/system/velocity.service:
[Unit]
Description=Velocity Minecraft Proxy
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=velocity
Group=velocity
WorkingDirectory=/opt/velocity
ExecStart=/usr/bin/java -Xms512M -Xmx1G \
-XX:+UseG1GC -XX:G1HeapRegionSize=4M \
-XX:+UnlockExperimentalVMOptions -XX:+ParallelRefProcEnabled \
-XX:+AlwaysPreTouch \
-jar velocity.jar
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
1 GB heap is plenty for 100 concurrent players. Velocity is much lighter than the backends. systemd unit reference.
sudo systemctl daemon-reload
sudo systemctl enable --now velocity
sudo journalctl -u velocity -f
Look for Listening on /0.0.0.0:25565 — that's ready.
Firewall
sudo ufw allow 25565/tcp
Minecraft is TCP. UDP not needed for Java edition. UFW rules.
Plugins
Drop into /opt/velocity/plugins/. Velocity-compatible plugins, not BungeeCord ones — the platform is different.
Standard set for a small network:
- LuckPerms-Velocity — permissions (setup guide)
- Signed-Velocity — signed-message handling for 1.19.1+
- TAB — tab list customization
- Velocity-Bridge or PluginMessageBridge — for cross-server chat or features
Restart after dropping a JAR; Velocity doesn't hot-load plugins.
Verify everything works
From a Minecraft client:
- Connect to
<your-public-ip>:25565. You should land on the lobby. /server survival— should switch you to the survival backend.- On the survival backend's console:
whois <yourname>. UUID shown should be your real Mojang UUID, not an offline-mode one starting with predictable digits. - Disconnect from the proxy, try connecting directly to
<public-ip>:25566. Should fail or land on the unhardened backend — if it works, your firewall isn't blocking the backend port.
Gotchas
- Forwarding secret mismatch. The single most common failure. Backend logs say
Unable to authenticate player. Fix: re-paste the secret exactly. Trailing whitespace counts. - Spigot can't do modern forwarding. Either upgrade backends to Paper, or fall back to
player-info-forwarding-mode = "legacy"(BungeeCord-compatible, less secure). velocity.tomlis hand-managed. Velocity does NOT overwrite the file on shutdown — your config is yours. Restart to apply changes, no/reloadequivalent.- 1.8 / 1.12 clients can't authenticate via modern forwarding. They need
bungeeguardmode and the BungeeGuard plugin on each backend. If your audience uses old launchers, plan for this. - Don't run Velocity on Java 11. It boots and looks fine, then misbehaves under load. Java 17 is the floor, Java 21 is the recommendation.
Why Velocity beats the alternatives
- vs BungeeCord/Waterfall: modern signed forwarding, lower latency, active development. The comparison post covers this in detail.
- vs no proxy at all: lets you have separate worlds (lobby / survival / creative) on different servers without players relaunching. Plus crash isolation — one backend going down doesn't kill the network.
- vs a hosting panel that bundles everything: you control the version, the JVM tuning, the plugins. No vendor lock-in.
For a fully fleshed network, follow up with LuckPerms cross-server permissions and Paper as the backend.