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.
| Attack | What it exhausts | Where it has to be stopped |
|---|---|---|
| Volumetric UDP or amplification flood | Your uplink; the server never sees most of it | Upstream, on a network bigger than the flood |
| SYN flood or ACK flood on 25565 | Kernel connection state, firewall CPU | At the network edge, at packet rate |
| Connection flood (handshakes opened and held) | Proxy sockets, threads and memory | Edge filtering plus proxy timeouts |
| Server list ping (status) flood | CPU spent building status replies | Protocol-aware filtering or cached replies |
| Bot join flood | Login handling, authentication, chat and world load | Proxy throttles and anti-bot checks |
| Bedrock or Geyser flood on 19132 | Link capacity and UDP socket buffers | UDP filtering that keeps real Bedrock traffic flowing |
| Query flood on the UDP query port | CPU, if enable-query is on | Turn 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.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
#!/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
| Control | Default | Helps against | Does not help against |
|---|---|---|---|
| Velocity login-ratelimit | 3,000 ms between connections from one IP | One address reconnecting rapidly | Bots spread across many addresses |
| BungeeCord connection_throttle and connection_throttle_limit | 4,000 ms, 3 connections | Rapid reconnects from one IP | Distributed join floods |
| Paper packet-limiter | Kick at an average of 500 packets per second per connection, measured over 7 s | Packet spam from a joined client | Floods of new connections, each sending only a few packets |
| Velocity packet-limiter | Decompressed input averaging 5,242,880 bytes per second | Compression bombs | Volumetric floods |
| Anti-bot plugins (category) | None until installed | Fake joins, by verifying players before they reach a backend | Anything 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
# 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 | head08Protected 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.
| Option | Hides backend | Absorbs large floods | Bedrock UDP | Real player IPs |
|---|---|---|---|---|
| Home connection with port forwarding | No | No | No | Yes |
| Velocity on a VPS with no upstream filtering | Yes | No; the VPS becomes the target | No | Yes |
| Game host with built-in filtering | Only through its proxy layer | Yes, if its network is large enough | Depends on the host | Yes |
| Minecraft TCP proxy service | Yes | Yes | Often Java only | Via the PROXY protocol |
| GRE or IP tunnel to your own machine | Yes, if the tunnel endpoint stays private | Yes | Yes, any protocol | Yes, addresses are preserved |
Questions
Frequently asked questions
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.
- 01Securing your servers · PaperMC (Velocity docs)
- 02Configuring player information forwarding · PaperMC (Velocity docs)
- 03default-velocity.toml (login-ratelimit, haproxy-protocol, packet-limiter defaults) · PaperMC on GitHub
- 04Paper global configuration (proxies, packet-limiter) · PaperMC
- 05Paper source: packet limiter in Connection.java.patch (rate per second over the interval) · PaperMC on GitHub
- 06BungeeCord configuration guide · SpigotMC
- 07Geyser setup: ports and NetherNet · GeyserMC
- 08server.properties · Minecraft Wiki
- 09Java Edition protocol: Server List Ping · Minecraft Wiki
- 10The PROXY protocol, versions 1 and 2 · HAProxy
- 11How a Dorm Room Minecraft Scam Brought Down the Internet (13 December 2017) · Wired
Last updated