Why run a proxy layer

A proxy layer collapses a whole network to one public address. Players connect to the proxy; it hands them to a lobby, then to survival, minigames or a queue, without them ever seeing a backend IP. You get one entry to protect, instant server switches for players, and the freedom to restart a backend without dropping the network.

The operational win is per-server maintenance. Restart the survival server for a plugin update and players sitting in the lobby never notice. Add a new minigame node and it joins the network by registering with the proxy, no DNS change, no new public IP. The security win is bigger: the only machine exposed to the internet is the proxy, so that is the only machine an attacker can target.

Velocity vs BungeeCord

Use Velocity unless a plugin you cannot replace only exists for BungeeCord. Velocity is faster, actively maintained, and its modern forwarding closes the IP-spoofing holes that plagued Bungee. BungeeCord is justified only by legacy plugin dependencies you are stuck with.

Velocity BungeeCord
Performance Event-driven, low overhead, scales to thousands per instance Older threading model, heavier under load
Forwarding security Modern forwarding with a shared secret; backend rejects unsigned joins Legacy forwarding was spoofable unless firewalled correctly
Plugin ecosystem Growing; most mainstream network plugins ported Large legacy library, some plugins Bungee-only
Our recommendation Default choice Only for an unavoidable legacy plugin

The security point deserves emphasis. Bungee's original forwarding trusted whatever player identity the connecting host claimed, so a backend reachable from the internet could be fed forged UUIDs. Velocity's modern mode signs the forwarded data with a secret only the proxy and backend share, per the Velocity official documentation on forwarding modes. A join without a valid signature is refused.

Target architecture

The layout that holds: players reach a protected public IP, that IP terminates on Velocity, and Velocity talks to backends over a private network they never expose publicly. Nothing behind the proxy has a route to the internet on the game port.

Backends bind to their private address only. The public internet has no path to port 25565 on any backend; the single firewall rule further down enforces it. This is the difference between a network that shrugs off an attack and one where the attacker skips the proxy and hits the survival server directly.

Velocity configuration that matters

Four settings carry the weight in velocity.toml: modern forwarding, the forwarding secret, a login rate limit, and the compression threshold. Set these and most of the abuse surface closes.

toml
# velocity.toml
bind = "0.0.0.0:25565"
motd = "<yellow>Your Network</yellow>"
show-max-players = 500
online-mode = true
# Sign forwarded player data so backends reject spoofed joins.
player-info-forwarding-mode = "modern"
forwarding-secret-file = "forwarding.secret"
prevent-client-proxy-connections = true

[servers]
lobby = "10.10.0.11:25565"
survival = "10.10.0.12:25565"
try = ["lobby"]

[advanced]
compression-threshold = 256
compression-level = -1
login-ratelimit = 3000        # min ms between connection attempts per IP
connection-timeout = 5000
read-timeout = 30000

[query]
enabled = false               # do not expose a public query listener

Generate the secret once and keep it private — it is the credential that lets a backend trust the proxy. The login-ratelimit throttles reconnect spam per source, and turning the query listener off removes a cheap amplification and reconnaissance vector.

Backend hardening

This is the section most networks get wrong. The backend must accept modern forwarding with the same secret, bind to the private interface, and drop any connection to 25565 that does not come from the proxy. Skip the firewall step and the private VLAN is theatre.

On modern Paper, enable Velocity forwarding in config/paper-global.yml:

yaml
# paper-global.yml — accept modern forwarding from the proxy
proxies:
  velocity:
    enabled: true
    online-mode: true
    secret: "<same secret as forwarding.secret>"

Bind the backend to its private address in server.properties (server-ip=10.10.0.12) and set online-mode=false there, because the proxy already authenticated the player — the forwarding secret is now what proves the connection is trusted. Then lock the port so only the proxy can reach it:

bash
# Only the proxy's private IP may reach the backend game port. Everything else is dropped.
iptables -A INPUT -p tcp --dport 25565 -s {{PROXY_PRIVATE_IP}} -j ACCEPT
iptables -A INPUT -p tcp --dport 25565 -j DROP
Setting online-mode=false on a backend that is reachable from the internet is a full takeover risk. It is only safe when the firewall rule above is in place and the backend has no public route to 25565. Verify the DROP rule before you disable online-mode.

Putting the proxy behind protection

The proxy is now your single exposed machine, so it is the machine that needs DDoS protection. Two ways to get it: run the proxy on a protected plan, or keep it where it is and tunnel to a scrubbing edge.

Option A — protected plan. Host the proxy on a protected Minecraft or VPS plan so filtering is on from activation and there is nothing to route. Our Minecraft server hosting puts the proxy behind the Frankfurt edge directly, and backends running Paper pair naturally with our Paper hosting plans on the same private network.

Option B — proxy anywhere, tunnel to us. If the proxy must live on another network, forward its traffic through our edge with a tunnel; the mechanics are in the remote DDoS protection with a GRE tunnel guide. If you front the proxy with a TCP balancer such as HAProxy, enable the PROXY protocol on both the balancer and Velocity so the real player IP survives the extra hop — without it, every player appears to come from the balancer and per-IP limits and bans break.

Common failure modes

Three mistakes account for most compromised proxy networks: a backend that was reachable from the internet, a missing or shared-too-widely forwarding secret, and a status response that leaks backend detail.

  • Backend found by mass scanners. A survival server that once listened on a public IP gets catalogued by Shodan and similar scanners, and attackers connect straight to it, bypassing the proxy. Bind to the private interface and enforce the firewall DROP rule so there is nothing to find.
  • Missing forwarding secret. Without modern forwarding and a matching secret, a reachable backend accepts forged identities — the classic UUID-spoofing takeover. The secret plus the firewall rule close it.
  • Status and MOTD leaking backend details. Verbose server-list responses or plugin version strings on the public listener hand attackers a map of your stack. Keep the public MOTD generic and the query listener off.

FAQ

Does the proxy add ping?

On the same network as its backends, under a millisecond — the extra hop is a local switch. The ping players see is dominated by their distance to the datacentre, not the proxy hop.

One protected IP for many backends?

Yes — that is the main reason to run a proxy. One public IP terminates on Velocity, which routes players to any number of backends on the private network, each with no public IP of its own.

Geyser behind Velocity?

Yes. Run Geyser with Floodgate so Bedrock players reach it over UDP 19132 while Java players use TCP; both land on the same backends. Keep the Geyser UDP listener behind the same protection as the Java port.

Max players per proxy instance?

Velocity handles thousands of concurrent connections per instance on modest hardware — the backend TPS is the bottleneck, not the proxy. When you outgrow one, run several behind the same protected IP.

Next step

Stand up Velocity with the config above, lock the backends behind the firewall rule, and confirm a direct connection to a backend IP is refused before you go live. If the attacks are already volumetric, read how to protect a Minecraft server from DDoS for the layer that absorbs them, then host the proxy on a protected plan.