If you read Waterfall vs BungeeCord and decided you actually want Velocity, this is the migration. Did this for a 4-backend network last year — about three hours of work, mostly translating plugin configs.
TL;DR:
sudo systemctl stop bungeecord
mkdir /opt/velocity && cd /opt/velocity
wget https://api.papermc.io/v2/projects/velocity/versions/latest/builds/latest/downloads/velocity.jar
java -jar velocity.jar # generates velocity.toml, then Ctrl-C
Then translate config.yml → velocity.toml, swap every Bungee plugin for its Velocity counterpart, set every backend's server.properties to use modern IP forwarding instead of BungeeCord-style. Details below.
Why migrate at all
Three reasons: lower latency under load, active development (Waterfall is in maintenance mode), and the plugin ecosystem has caught up — every plugin I needed has a Velocity build by 2026.
If your network is fine and you're not hitting walls, don't migrate. The juice isn't worth the squeeze for a 20-player friends server. For anything bigger, or anything you expect to keep running 3+ years, do it.
Pre-flight
Before touching anything:
sudo systemctl stop bungeecord
cp -r /opt/bungeecord /opt/bungeecord.bak.$(date +%F)
Full directory copy. If the migration goes sideways, mv brings the old proxy back in 30 seconds. Pattern from rsync backups on Linux.
Make a list of every plugin in /opt/bungeecord/plugins/ and its config. You'll re-create each one.
ls /opt/bungeecord/plugins/ > /tmp/plugin-list.txt
Install Velocity
Velocity wants Java 17+. Check:
java -version
If it's older, install Temurin 21 — Velocity runs better on it:
sudo apt install temurin-21-jdk
Get the JAR:
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, forwarding.secret, and the plugins/ folder, then asks you to set up. Ctrl-C — we're going to write the config from scratch.
velocity.toml — translation from config.yml
Velocity uses TOML, not YAML. The mental mapping:
config-version = "2.7"
bind = "0.0.0.0:25565"
motd = "&6&lThe 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"
creative = "127.0.0.1:25568"
try = ["lobby"]
[forced-hosts]
"survival.example.com" = ["survival"]
"creative.example.com" = ["creative"]
[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
announce-forge = false
kick-existing-players = false
ping-passthrough = "DISABLED"
sample-players-in-ping = false
[query]
enabled = false
port = 25565
map = "Velocity"
show-plugins = false
Field-by-field map from BungeeCord's config.yml (see my BungeeCord config.yml deep-dive for the source side):
| BungeeCord | Velocity |
|---|---|
listeners[0].host |
bind |
listeners[0].motd |
motd |
listeners[0].max_players |
show-max-players |
listeners[0].forced_hosts |
[forced-hosts] |
listeners[0].priorities |
[servers].try |
listeners[0].tab_list |
(gone — Velocity handles tab via plugin) |
servers |
[servers] (flat key=value) |
ip_forward: true |
player-info-forwarding-mode = "modern" |
online_mode |
online-mode |
connection_throttle |
advanced.login-ratelimit |
network_compression_threshold |
advanced.compression-threshold |
The IP forwarding switch
This is the only step that requires touching every backend.
BungeeCord-style forwarding works by trusting whatever the proxy puts in the handshake. Modern (Velocity) forwarding signs the player data with a shared secret — backends verify the signature, so a malicious connection that bypassed the proxy fails.
Velocity generated /opt/velocity/forwarding.secret on first run. Copy its contents — that's the shared secret.
On every backend (Paper 1.13+ required for modern forwarding), edit config/paper-global.yml:
proxies:
velocity:
enabled: true
online-mode: true
secret: "<contents-of-forwarding.secret>"
And server.properties:
online-mode=false
Yes, still false — same contract as BungeeCord. The proxy authenticates, the backend trusts. The difference is the trust is now cryptographically verified.
Restart each backend. If a backend is on Spigot or older Paper, it can't do modern forwarding — either upgrade to current Paper or fall back to player-info-forwarding-mode = "legacy" in Velocity (BungeeCord-compatible, less secure).
Plugin replacements
Every BungeeCord plugin needs a Velocity equivalent. The common ones:
| BungeeCord | Velocity |
|---|---|
| LuckPerms-Bungee | LuckPerms-Velocity |
| BungeeTabListPlus | TAB (works on Velocity) |
| RedisBungee | RedisBungee-Velocity (community fork) |
| BungeeCord-Authenticator | nLogin / AuthMeVelocity |
| ServerListMOTD | MotdManager |
Move configs over carefully — most plugins keep similar config structures between platforms, but YAML key names sometimes differ. Read the plugin's Velocity README before assuming the old config drops in.
Drop new JARs into /opt/velocity/plugins/, restart Velocity, watch the log:
sudo -u velocity tail -f /opt/velocity/logs/latest.log
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 a 100-player network. More on systemd units in systemd service unit files.
sudo systemctl daemon-reload
sudo systemctl enable --now velocity
DNS / firewall — nothing changes
Velocity binds to the same port (25565). DNS records, UFW rules, forced_hosts domains — all unchanged. You're swapping the engine, not the address.
Verifying
From a client, connect. Tab list, server-switch (/server lobby), permissions — all should work like before. Check that the backends see real player UUIDs:
grep -E "^\[..:..:..\] .* logged in" /opt/paper/lobby/logs/latest.log | tail
UUIDs should be Mojang UUIDs, not offline-mode generated ones. If they're offline UUIDs, modern forwarding isn't actually active — re-check the secret on both sides.
Rolling back
Worst case:
sudo systemctl stop velocity
sudo systemctl start bungeecord # if you kept the unit
And revert each backend's config/paper-global.yml to disable modern forwarding. Five minutes if you took the pre-flight backups.
Gotchas
- Forwarding secret must match exactly. Trailing newlines, accidental spaces — the JVM is unforgiving. Copy via
cat /opt/velocity/forwarding.secretand paste literally. - Spigot can't do modern forwarding. Migrate backends to Paper first if any are on Spigot.
- Velocity does not auto-overwrite
velocity.tomlon shutdown. This is the opposite of BungeeCord, which is good — your config is yours. - Player permissions don't transfer. LuckPerms data carries over (it's per-user, not per-platform), but if you used BungeeCord's built-in permissions, you have to re-add them to LuckPerms. Build the LuckPerms config first, test on staging, then migrate prod.
- Some 1.8 / 1.12 clients can't connect via modern forwarding. Velocity supports them, but you need
player-info-forwarding-mode = "bungeeguard"(BungeeGuard plugin on backends) instead ofmodern. Fine for legacy compatibility, slightly less clean.
If you're running a fresh new network, this is one of the first decisions. If you're migrating, do it on a Sunday morning and keep the BungeeCord directory around for two weeks before deleting it.