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 config
  • forwarding.secret — random secret for modern IP forwarding
  • plugins/ — empty
  • logs/ — 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 to 127.0.0.1 only 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. The try list is the fallback chain on join (lobby first, lobby2 if lobby is down, etc.).
  • [forced-hosts] — domain-based routing. survival.example.com skips the lobby and lands on survival.

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:

  1. Connect to <your-public-ip>:25565. You should land on the lobby.
  2. /server survival — should switch you to the survival backend.
  3. 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.
  4. 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.toml is hand-managed. Velocity does NOT overwrite the file on shutdown — your config is yours. Restart to apply changes, no /reload equivalent.
  • 1.8 / 1.12 clients can't authenticate via modern forwarding. They need bungeeguard mode 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.


Related posts