Guides

8 min readUpdated

How to protect a Minecraft server from DDoS attacks

Quick answer

To protect a Minecraft server from DDoS, put players on a protected proxy address and never let them reach the machine that runs the world. Run Velocity or BungeeCord as the only public entry point, forward player data with a shared secret, firewall the backend so only the proxy can connect, and host the proxy on a network that filters SYN floods, bot joins and Bedrock UDP without null-routing. Plugins help against bot joins, not floods.

01Why do Minecraft servers get DDoSed?

Because it pays and it is easy. Popular networks compete for the same players, the server address is public, and a few minutes of lag pushes players to a rival. Attack services are cheap to rent, and many attackers are players with a grudge rather than skilled criminals.

The incentive is well documented. FBI investigators working on the Mirai botnet noticed that many of its attacks hit gaming servers and traced the motive to the Minecraft hosting market. One agent told Wired that popular servers were making $100,000 a month at the peak of summer, and Wired reported that this income had spawned a cottage industry of DDoS attacks on competitors' servers to lure their players away.

So assume your address will be found, keep the machine that matters unreachable, and expect attacks on the Minecraft protocol itself, not just on bandwidth.

02What kinds of DDoS hit a Minecraft server?

Each pattern exhausts something different, which decides where it can be stopped.

AttackWhat it exhaustsWhere it has to be stopped
Volumetric UDP or amplification floodYour uplink; the server never sees most of itUpstream, on a network bigger than the flood
SYN flood or ACK flood on 25565Kernel connection state, firewall CPUAt the network edge, at packet rate
Connection flood (handshakes opened and held)Proxy sockets, threads and memoryEdge filtering plus proxy timeouts
Server list ping (status) floodCPU spent building status repliesProtocol-aware filtering or cached replies
Bot join floodLogin handling, authentication, chat and world loadProxy throttles and anti-bot checks
Bedrock or Geyser flood on 19132Link capacity and UDP socket buffersUDP filtering that keeps real Bedrock traffic flowing
Query flood on the UDP query portCPU, if enable-query is onTurn query off, or filter it

03Hide the backend behind Velocity or BungeeCord

The most effective step is architectural: players only ever connect to a proxy, and the machine holding your world is never reachable from the internet. Velocity and BungeeCord do exactly this. They accept player connections, authenticate them with Mojang and pass them on to backend servers.

Use Velocity's modern forwarding where you can. It passes the player's address, UUID and skin to the backend protected by a shared secret, so a stranger cannot pose as your proxy. It needs Minecraft 1.13 or newer; older versions can use legacy BungeeCord forwarding or the BungeeGuard variant, which adds a secret token. Paper backends accept Velocity forwarding with online-mode switched off, because the proxy has already authenticated the player.

Then make sure nothing points at the backend:

  • DNS: the A record and any _minecraft._tcp SRV record point at the proxy, never the backend; old records live on in DNS history services
  • Web maps, vote listeners and statistics pages on the backend's address
  • Panels, SFTP hostnames and status bots that print the address
  • Plugins making outbound HTTP requests, which show the backend's address to whoever runs the URL
  • A backend that was once public needs a new address
Velocity proxy and Paper backend, excerpts (parsed as TOML and YAML; addresses are examples)
# velocity.toml (excerpt) - the proxy players connect to
bind = "0.0.0.0:25565"
online-mode = true
player-info-forwarding-mode = "modern"
forwarding-secret-file = "forwarding.secret"

[servers]
lobby = "10.0.0.2:25565"
try = ["lobby"]

[advanced]
login-ratelimit = 3000
# true only if a PROXY-protocol provider sits in front of Velocity
# and nothing else can reach this port
haproxy-protocol = false

# config/paper-global.yml (excerpt) - on each backend server
proxies:
  velocity:
    enabled: true
    online-mode: true
    secret: "paste the contents of forwarding.secret here"

04Firewall the backend so only the proxy can connect

Drop every packet to the backend's game port that does not come from one of your proxies. Velocity's documentation strongly recommends an operating-system firewall; IP-whitelist plugins are only a backup, because by the time they run the connection has reached Java.

If proxy and backend share a machine, bind the backend to loopback (server-ip=127.0.0.1). Otherwise connect them over a private network or encrypted tunnel and use an allowlist like this one, which was load-tested between two isolated network namespaces: listed proxy addresses connected, unlisted ones were dropped.

  • Load it with nft -f; it adds its own table, and re-running it replaces only that table
  • The policy stays accept so SSH is untouched; a default-drop firewall is better, built deliberately
  • List every proxy address, including IPv6
  • If a provider hands you connections using the PROXY protocol, set haproxy-protocol = true in velocity.toml and lock Velocity's port to that provider; the specification warns that otherwise anyone can forge their address
  • Close RCON (TCP 25575 by default) and query to the world
nftables allowlist for the backend (nft 1.0.6, Debian 12)
#!/usr/sbin/nft -f
# Minecraft backend: only your Velocity proxies may reach the game port.
# Re-running the file replaces this table and leaves all other rules alone.
table inet mc_backend
delete table inet mc_backend

table inet mc_backend {
    set proxy4 {
        type ipv4_addr
        flags interval
        elements = { 10.0.0.1, 192.0.2.10 }
    }
    set proxy6 {
        type ipv6_addr
        flags interval
        elements = { 2001:db8:10::/64 }
    }

    chain input {
        type filter hook input priority filter; policy accept;

        iif "lo" accept
        tcp dport 25565 ip saddr @proxy4 accept
        tcp dport 25565 ip6 saddr @proxy6 accept

        # everyone else: game port, and the query port if enable-query is on
        tcp dport 25565 counter drop
        udp dport 25565 counter drop
    }
}

05What your host's DDoS protection must handle

A proxy hides the backend; it absorbs nothing. The address players connect to is public by design, so it must sit on a network that takes the flood and still passes real players. When comparing hosts or proxy providers, check for:

  • TCP floods filtered at packet rate without disconnecting players already in game
  • Protocol awareness: every Java connection opens with a handshake declaring status, login or transfer, so traffic that does not parse can be dropped early
  • Status-ping floods answered or rate-limited at the edge, not passed to Velocity one by one
  • Bot-join floods throttled per source and in total
  • Bedrock UDP on 19132 filtered rather than discarded, plus TCP on that port for newer clients
  • IPv6 protected as well as IPv4, and no null-routing

06What plugins and server settings can do

Settings and plugins are the last layer. They act on traffic that has already reached Java, so they help against bot joins and abusive clients and do nothing against a flood that fills the pipe. Defaults below are from each project's documentation and default configuration; the Paper rate is as the server code applies it:

  • Prefer anti-bot tools that verify players before they reach a backend, such as a check on the proxy or a limbo server
  • Prefer open-source plugins you can audit; a plugin runs with your server's full privileges
ControlDefaultHelps againstDoes not help against
Velocity login-ratelimit3,000 ms between connections from one IPOne address reconnecting rapidlyBots spread across many addresses
BungeeCord connection_throttle and connection_throttle_limit4,000 ms, 3 connectionsRapid reconnects from one IPDistributed join floods
Paper packet-limiterKick at an average of 500 packets per second per connection, measured over 7 sPacket spam from a joined clientFloods of new connections, each sending only a few packets
Velocity packet-limiterDecompressed input averaging 5,242,880 bytes per secondCompression bombsVolumetric floods
Anti-bot plugins (category)None until installedFake joins, by verifying players before they reach a backendAnything that saturates the link

07How to tell your Minecraft server is under attack

Start with the symptom; the general rules for telling an attack from lag are in signs of a DDoS on a game server. Players timing out while CPU and TPS look normal means the link is full. A console flooded with joins from random names is a bot attack. A proxy slow to answer pings while the backend idles suggests a status or connection flood. These read-only checks on the proxy tell them apart:

  • Thousands of sources each sending a few SYNs: a distributed SYN flood that needs edge filtering
  • A few sources holding many connections each: a connection flood that per-source limits can handle
  • A rising TcpExtSyncookiesSent counter: the kernel is already falling back to SYN cookies
  • UDP to 19132 from addresses that never joined: a Bedrock flood that needs upstream filtering
Read-only checks (filters compiled with tcpdump 4.99.3; ss and nstat lines run against crafted connections)
# SYNs arriving for the Java port: watch the rate and the spread of sources
tcpdump -nni eth0 'tcp dst port 25565 and tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn'

# Bedrock / Geyser UDP
tcpdump -nni eth0 'udp dst port 19132'

# half-open connections, and the kernel's SYN-cookie and backlog counters
ss -Htn state syn-recv '( sport = :25565 )' | wc -l
nstat -az TcpExtSyncookiesSent TcpExtListenOverflows TcpExtListenDrops

# established connections per source address, top 10
ss -Htn state established '( sport = :25565 )' | awk '{print $4}' \
  | sed 's/:[0-9]*$//' | sort | uniq -c | sort -rn | head

08Protected host, game proxy or tunnel: which to choose?

Each option moves the public address elsewhere; they differ in what they filter and how much control you keep. The rules above still apply: one public address on the protected network, a backend that only answers the proxy, nothing published that points around it.

For tunnels, see what GRE tunnelling is and remote DDoS protection. GuardX's GRE transit, which will deliver filtered TCP and UDP to a machine you already run, is marked coming soon; if it would suit you, get in touch.

OptionHides backendAbsorbs large floodsBedrock UDPReal player IPs
Home connection with port forwardingNoNoNoYes
Velocity on a VPS with no upstream filteringYesNo; the VPS becomes the targetNoYes
Game host with built-in filteringOnly through its proxy layerYes, if its network is large enoughDepends on the hostYes
Minecraft TCP proxy serviceYesYesOften Java onlyVia the PROXY protocol
GRE or IP tunnel to your own machineYes, if the tunnel endpoint stays privateYesYes, any protocolYes, addresses are preserved

Questions

Frequently asked questions

Only from part of it. Plugins run inside Java on a server the traffic has already reached, so they can throttle bot joins, kick packet spammers and verify players, but they cannot stop a flood that fills your link or exhausts the kernel. Volumetric floods and SYN floods have to be filtered upstream, before the traffic reaches your machine.

No. An SRV record only tells the client which host and port to connect to, and anyone can look it up. It gives players a clean hostname on a custom port, but its target must be your protected proxy. An SRV record pointing at the backend publishes exactly the address you are trying to hide.

Only behind a proxy and a firewall. With online-mode off, the backend trusts whatever identity the connection claims, so anyone who can reach it directly can join as any player. Keep online-mode on at the proxy, enable modern forwarding with a secret, and firewall the backend so that only your proxies can connect to it.

Not on its own. If the old address leaked through DNS history, a plugin, a web map or a hosting panel, the new one will leak the same way. Fix the leak first, move players to a protected proxy address, then change the backend's address and firewall it.

Not an ordinary one. HTTP reverse proxies only carry web traffic, and Minecraft speaks its own protocol over TCP for Java and UDP for Bedrock. You need a TCP-level proxy that passes the Minecraft protocol, a game host with built-in filtering, or a tunnel that carries any protocol to your own machine.

Sources

Sources and verification

nftables allowlist syntax-checked with nft 1.0.6 and load-tested, unchanged, between two isolated network namespaces over a veth pair (listed IPv4 proxy addresses connected, unlisted sources dropped and counted); tcpdump filters compiled with tcpdump 4.99.3; ss and nstat (iproute2 6.1.0) commands run; velocity.toml and paper-global.yml excerpts parsed with Python 3.11 tomllib and PyYAML. Debian 12, October 2026. Velocity and Paper themselves were not run.

  1. 01Securing your servers · PaperMC (Velocity docs)
  2. 02Configuring player information forwarding · PaperMC (Velocity docs)
  3. 03default-velocity.toml (login-ratelimit, haproxy-protocol, packet-limiter defaults) · PaperMC on GitHub
  4. 04Paper global configuration (proxies, packet-limiter) · PaperMC
  5. 05Paper source: packet limiter in Connection.java.patch (rate per second over the interval) · PaperMC on GitHub
  6. 06BungeeCord configuration guide · SpigotMC
  7. 07Geyser setup: ports and NetherNet · GeyserMC
  8. 08server.properties · Minecraft Wiki
  9. 09Java Edition protocol: Server List Ping · Minecraft Wiki
  10. 10The PROXY protocol, versions 1 and 2 · HAProxy
  11. 11How a Dorm Room Minecraft Scam Brought Down the Internet (13 December 2017) · Wired

Last updated