What Is Velocity?
Velocity is a modern Minecraft proxy software developed by the PaperMC team. It brings multiple Minecraft servers together under a single IP, letting players move between servers seamlessly. It is the modern, secure and performant alternative to BungeeCord.
BungeeCord vs Velocity Comparison
| Feature | BungeeCord | Velocity |
|---|---|---|
| Developer | SpigotMC (md_5) | PaperMC |
| Performance | Medium — its older architecture uses more CPU/RAM | High — up to 50% lower resource usage |
| Forwarding | BungeeGuard or legacy IP forwarding | Modern forwarding (HMAC-based, secure) |
| Security | IP spoofing vulnerability (requires an extra plugin) | Built-in security, UUID spoofing protection |
| Plugin API | Old, callback-based | Modern, event-driven, CompletableFuture support |
| Backend Support | Spigot, Paper | Paper, Purpur, Fabric, Forge |
| DDoS Protection | None (a plugin is required) | Built-in rate limiting |
| 1.21+ Compatibility | Yes | Yes — priority support |
| Active Development | Slow | Very active |
| Community | Large (legacy) | Growing (modern) |
Installing Velocity
Create a separate user and directory for Velocity. The proxy runs independently of the backend servers.
Requirements
- Java 17 or newer
- Minimum 512 MB RAM (for the proxy)
- A separate port (default: 25577)
- Additional RAM and ports for the backend servers
Download and First Start
# Create the Velocity directory
mkdir -p ~/velocity && cd ~/velocity
# Download the latest build (3.4.0 is an example — check the current version)
wget https://api.papermc.io/v2/projects/velocity/versions/3.4.0/builds/latest/downloads/velocity-3.4.0.jar
# First start — let the config files be generated
java -Xms512M -Xmx512M -jar velocity-3.4.0.jar
# Stop it with Ctrl+CAfter the first start the following files are created:
Directory Structure
~/velocity/
├── velocity-3.4.0.jar # Main proxy file
├── velocity.toml # Main configuration
├── forwarding.secret # Modern forwarding key
├── server-icon.png # Server list icon (64x64)
├── plugins/ # Velocity plugins
└── logs/ # Log filesvelocity.toml Configuration
velocity.toml is the main config file that defines every aspect of the proxy's behaviour. We go through each setting in detail:
General Settings
| Setting | Default | Recommended | Description |
|---|---|---|---|
| <code>bind</code> | 0.0.0.0:25577 | 0.0.0.0:25577 | The IP and port the proxy listens on |
| <code>motd</code> | A Velocity Server | Custom MOTD | The description shown in the server list (supports colour codes) |
| <code>show-max-players</code> | 500 | 500 | Maximum player count shown in the server list |
| <code>online-mode</code> | true | true | Mojang authentication (premium check) |
| <code>force-key-presence</code> | true | true | Require chat signing keys (1.19.1+) |
| <code>player-info-forwarding-mode</code> | NONE | modern | IP/UUID forwarding mode (modern = the most secure) |
| <code>announce-forge</code> | false | false | Send the mod list to Forge clients |
# velocity.toml — General settings
bind = "0.0.0.0:25577"
motd = "&b&lKEYDAL &fNetwork &7| &aWelcome to our server!"
show-max-players = 500
online-mode = true
force-key-presence = true
player-info-forwarding-mode = "modern"Server Definitions
You define the backend servers in the [servers] section. Each server consists of a name and an IP:port pair.
[servers]
lobby = "127.0.0.1:25565"
survival = "127.0.0.1:25566"
skyblock = "127.0.0.1:25567"
creative = "127.0.0.1:25568"
# Servers tried in order when a player first connects
try = ["lobby"]Forced Hosts
You can route different domain names straight to different servers:
[forced-hosts]
"play.example.com" = ["lobby"]
"survival.example.com" = ["survival"]
"skyblock.example.com" = ["skyblock"]
"creative.example.com" = ["creative"]All domain names must point in DNS to the same server IP and to the Velocity port (25577). Velocity then routes to the correct backend based on the incoming hostname.
# DNS A records (all to the same IP)
play.example.com A 185.x.x.x
survival.example.com A 185.x.x.x
skyblock.example.com A 185.x.x.x
# SRV record (required when the port is 25577)
_minecraft._tcp.play.example.com SRV 0 0 25577 play.example.comAdvanced Settings
| Setting | Default | Description |
|---|---|---|
| <code>login-ratelimit</code> | 3000 | Minimum time between login attempts (ms) |
| <code>connection-timeout</code> | 5000 | Backend connection timeout (ms) |
| <code>read-timeout</code> | 30000 | Data read timeout (ms) |
| <code>haproxy-protocol</code> | false | HAProxy PROXY protocol support (for TCPShield) |
| <code>ping-passthrough</code> | DISABLED | Take ping information from the backend (ALL, MODS, DESCRIPTION, DISABLED) |
[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-proxy-commands = true
log-command-executions = false
log-player-connections = trueAdding Backend Servers
Each backend server is an independent Paper/Purpur instance. It runs on its own port and has its own config files.
Backend Directory Structure
~/servers/
├── lobby/ # Port 25565
│ ├── paper.jar
│ ├── server.properties
│ └── config/paper-global.yml
├── survival/ # Port 25566
│ ├── paper.jar
│ ├── server.properties
│ └── config/paper-global.yml
└── skyblock/ # Port 25567
├── paper.jar
├── server.properties
└── config/paper-global.ymlserver.properties Settings
The following settings are critical in each backend server's server.properties file:
| Setting | Value | Description |
|---|---|---|
| <code>server-port</code> | 25565/25566/25567 | A different port for each server |
| <code>server-ip</code> | 127.0.0.1 | Only accept connections from localhost |
| <code>online-mode</code> | false | Authentication is handled by Velocity |
| <code>prevent-proxy-connections</code> | false | Do not block proxy connections |
Modern Forwarding Configuration
Modern forwarding is Velocity's HMAC-based secure IP/UUID forwarding system. Backend servers receive the player's real IP, UUID and skin data securely.
Step 1: The Velocity Side
# velocity.toml
player-info-forwarding-mode = "modern"This setting is already present by default. The forwarding.secret file is generated automatically on the first start.
Step 2: Copying the Secret
cat ~/velocity/forwarding.secret
# Example output: aB3dEf7GhI9jKlMnOpQrStUvWxYz12You need to copy this key to every backend server.
Step 3: Paper Backend Configuration
On each backend server, edit the config/paper-global.yml file:
# config/paper-global.yml
proxies:
velocity:
enabled: true
online-mode: true
secret: "aB3dEf7GhI9jKlMnOpQrStUvWxYz12" # exactly the same as forwarding.secretForwarding Mode Comparison
| Mode | Security | Description |
|---|---|---|
| <code>none</code> | None | No IP/UUID forwarding — the backend cannot receive the real data |
| <code>legacy</code> | Low | BungeeCord compatible — open to IP spoofing |
| <code>bungeeguard</code> | Medium | Legacy protected with a BungeeGuard token |
| <code>modern</code> | High | HMAC-based — only Velocity supports it, requires Paper/Purpur |
Switching Between Servers
Velocity ships built-in commands so players can move between servers:
| Command | Description | Permission |
|---|---|---|
| <code>/server lobby</code> | Switch to the specified server | velocity.command.server |
| <code>/server</code> | Show the server list | velocity.command.server |
| <code>/glist</code> | List the players on all servers | velocity.command.glist |
| <code>/send Player survival</code> | Send a player to the specified server | velocity.command.send |
| <code>/send all lobby</code> | Send all players to the lobby | velocity.command.send |
| <code>/velocity plugins</code> | List the installed Velocity plugins | velocity.command.plugins |
| <code>/velocity reload</code> | Reload velocity.toml | velocity.command.reload |
For a server selection menu (GUI) you can use plugins such as LobbyAPI, HubCommand or ServerSelectorX.
Security
Security is critical in a network setup. A wrong configuration opens the door to UUID spoofing, unauthorised access and DDoS attacks.
Firewall: Open Only the Proxy Port
Rule: only the Velocity port (25577) and SSH (22) should be open to the outside. The backend ports (25565-25567) must be unreachable from outside.
# Firewall settings with UFW
# SSH access
sudo ufw allow 22/tcp
# Velocity proxy port (open to the outside — the single entry point)
sudo ufw allow 25577/tcp
# Backend ports CLOSED — they already listen on 127.0.0.1
# But block them explicitly to be sure:
sudo ufw deny 25565/tcp
sudo ufw deny 25566/tcp
sudo ufw deny 25567/tcp
# Enable the firewall
sudo ufw enable
sudo ufw status verbose| Port | State | Description |
|---|---|---|
| 22/tcp | ALLOW | SSH access |
| 25577/tcp | ALLOW | Velocity proxy — the port players connect to |
| 25565/tcp | DENY | Lobby backend — localhost only |
| 25566/tcp | DENY | Survival backend — localhost only |
| 25567/tcp | DENY | Skyblock backend — localhost only |
Close Backend Port 25565
Use the server-ip=127.0.0.1 setting in the backend servers' server.properties file so they only accept connections from localhost:
# In every backend server's server.properties file:
server-ip=127.0.0.1
online-mode=falseIPWhitelist
To make backend servers accept only connections coming from Velocity's IP, you can use the IPWhitelist plugin:
- Install the IPWhitelist plugin on the backend servers
- In the config, allow only
127.0.0.1(or Velocity's IP) - Anyone who connects directly is rejected
DDoS Protection
- TCPShield — Free Minecraft DDoS protection, reverse proxy
- Cloudflare Spectrum — Paid, enterprise-grade TCP proxy
- OVH Game DDoS Protection — Built into OVH servers
- Velocity's built-in rate limiting — brute force protection through the
login-ratelimit: 3000setting
LuckPerms Network Setup
For permission management across the network you need to install LuckPerms on every server and use a shared database. That way permissions stay in sync on all servers.
MySQL Database Setup
# MySQL installation (Ubuntu/Debian)
sudo apt install mysql-server -y
sudo mysql_secure_installation
# Create the database and user for LuckPerms
sudo mysql -u root -p
CREATE DATABASE luckperms;
CREATE USER 'luckperms'@'localhost' IDENTIFIED BY 'your_strong_password';
GRANT ALL PRIVILEGES ON luckperms.* TO 'luckperms'@'localhost';
FLUSH PRIVILEGES;
EXIT;LuckPerms Config (On Every Server)
In the LuckPerms config.yml file on Velocity, lobby, survival and every backend server:
# plugins/LuckPerms/config.yml (the same on all servers)
storage-method: mysql
data:
address: localhost:3306
database: luckperms
username: luckperms
password: 'your_strong_password'
pool-settings:
maximum-pool-size: 10
# Instant synchronisation between servers
messaging-service: sqlServer-Scoped Permissions (Context)
With the LuckPerms context system you can assign permissions that apply only to certain servers:
# fly permission only on the survival server
lp user Player permission set essentials.fly true server=survival
# gamemode permission only on the creative server
lp group vip permission set essentials.gamemode true server=creative
# Permission valid on all servers (when no context is given)
lp group default permission set essentials.home trueStartup Script
Create a bash script to start every server with a single command:
#!/bin/bash
# start-network.sh
echo "=== Starting the KEYDAL Network ==="
# Start the backend servers with screen
echo "[1/4] Starting lobby..."
screen -dmS lobby bash -c 'cd ~/servers/lobby && java -Xms1G -Xmx1G -XX:+UseG1GC -jar paper.jar --nogui'
sleep 8
echo "[2/4] Starting survival..."
screen -dmS survival bash -c 'cd ~/servers/survival && java -Xms4G -Xmx4G -XX:+UseG1GC -jar paper.jar --nogui'
sleep 8
echo "[3/4] Starting skyblock..."
screen -dmS skyblock bash -c 'cd ~/servers/skyblock && java -Xms2G -Xmx2G -XX:+UseG1GC -jar paper.jar --nogui'
sleep 8
# Start the Velocity proxy last
echo "[4/4] Starting Velocity..."
screen -dmS velocity bash -c 'cd ~/velocity && java -Xms512M -Xmx512M -XX:+UseG1GC -jar velocity.jar'
echo ""
echo "=== All servers started! ==="
echo "To attach: screen -r <name>"
echo " screen -r lobby"
echo " screen -r survival"
echo " screen -r skyblock"
echo " screen -r velocity"chmod +x start-network.sh
./start-network.shSystemd Service
Create a systemd service so Velocity starts automatically when the machine reboots:
# /etc/systemd/system/velocity.service
[Unit]
Description=Velocity Minecraft Proxy
After=network.target
[Service]
User=minecraft
Group=minecraft
WorkingDirectory=/home/minecraft/velocity
ExecStart=/usr/bin/java -Xms512M -Xmx512M -XX:+UseG1GC -jar velocity.jar
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=velocity
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable velocity.service
sudo systemctl start velocity.service
sudo systemctl status velocity.serviceCommon Mistakes and Their Fixes
| Problem | Cause | Fix |
|---|---|---|
| Player cannot connect | The firewall is blocking port 25577 | Check <code>ufw allow 25577/tcp</code> |
| "Unable to connect to server" error | The backend server is offline or the port is wrong | Check that the backend is running and on the correct port |
| "If you wish to use IP forwarding" error | Modern forwarding is not configured | Enter the velocity secret in paper-global.yml |
| Skins do not show | Wrong forwarding mode | Check <code>player-info-forwarding-mode = "modern"</code> |
| UUID changes between servers | online-mode is inconsistent across the backends | <code>online-mode=false</code> on all backends and <code>online-mode: true</code> in paper-global |
| LuckPerms permissions are not in sync | Different storage, or messaging-service is missing | The same MySQL connection on all servers and <code>messaging-service: sql</code> |
| The connection drops when a player switches servers | Backend timeout or a network problem | Increase the <code>connection-timeout</code> and <code>read-timeout</code> values |
| velocity.toml changes are not applied | The config has not been reloaded | <code>/velocity reload</code> or restart the proxy |
| Forced hosts do not work | Wrong DNS records or a port mismatch | Check that every subdomain points to the same IP:25577 |
| The backend can be connected to directly | server-ip=127.0.0.1 is not set or the firewall is missing | Check server.properties and the firewall together |
Recommended Velocity Plugins
| Plugin | Function | Required? |
|---|---|---|
| <strong>LuckPerms (Velocity)</strong> | Network-wide permission management | Yes |
| <strong>SignedVelocity</strong> | Chat message signing support (1.19.1+) | Recommended |
| <strong>MiniMOTD</strong> | Customisable MOTD, server icon, player count | Optional |
| <strong>Geyser (Velocity)</strong> | Lets Bedrock players connect to a Java server | Optional |
| <strong>ViaVersion (Velocity)</strong> | Support for connecting from different MC versions | Recommended |
| <strong>LimboFilter</strong> | Antibot filter against bot attacks | Recommended |
| <strong>VelocityTools</strong> | Maintenance mode, MOTD management | Optional |
| <strong>NanoLimbo</strong> | Ultra-light limbo server (queue/maintenance) | Optional |