Protecting a Rust server from DDoS attacks

Published on 16 min read

Rust servers are almost always attacked at wipe or in the middle of a raid. What you can secure yourself, where self-protection hits physical limits, and what has to happen upstream in the network.

A Rust server rarely goes offline by accident. The timing almost always gives away the motive: the attack starts in the very minute of the wipe, or in the middle of a raid. If you are the one being shot at, you do not need a debate about principles, you need an order of steps. This article starts with what you can change on the server yourself, then shows where those options run out, and finally what has to happen upstream in the network.

Why Rust servers of all things get attacked so often

Rust is a game in which progress is tied to time. A raid takes minutes, a wipe cycle takes weeks. That makes an outage more valuable here than in almost any other game: if you are defending, you buy time when the server goes down. If you are attacking, you keep the other side from getting online. And if you run a competing community, you know that the first wipe evening decides the player count for the whole month.

On top of that comes one thing you cannot configure away: a Rust server is publicly findable by IP address and port, otherwise nobody could join. Unlike a website behind a proxy, a game server has to publish its real address. So the question is never whether the attacker finds your IP, only what happens once they start firing at it.

The ports this is about

In the usual configuration a Rust server occupies four ports:

  • 28015/UDP, the game port (server.port). All game traffic runs here. UDP has no connection setup, every packet stands on its own, and the source address can be forged. For an attacker that means no traceability, while your server still has to do work for every single packet.
  • The query port (server.queryport), also UDP. This is where the server answers the Steam queries A2S_INFO, A2S_PLAYERS and A2S_RULES; without it, the server appears in no server list at all. If you do not set an explicit value, it sits right next to the game port, and many start lines put it on 28017/UDP. Check your own start line instead of relying on a default.
  • 28016/TCP, RCON (rcon.port), running as the WebSocket variant with rcon.web 1.
  • 28082/TCP, the Rust+ companion app (app.port).

The query port is the most awkward of the four, because an A2S answer is considerably larger than the request. An attacker can query game servers belonging to other people with a forged source address and steer the answers at the real target. Your server is then not just a victim, it is an amplifier pointed at third parties. Valve added a challenge step to A2S_INFO for exactly this reason, which took the edge off the problem without ending it. How to recognize an attack in the first place is covered in the article Detecting a DDoS attack on your server.

What you can do yourself before spending money

The next part costs nothing and pays off no matter where your server is hosted. It will not take a volumetric attack off your hands, but it does make small attacks pointless, and it means you are not guessing when things get serious.

1. Take stock: what is actually listening

Before you write a single rule, work out which services are reachable. On a game server that has grown over time, there are almost always more of them than you expect:

ss -lntup

Anything bound to 127.0.0.1 or ::1 needs no opening in the firewall. Anything on 0.0.0.0 or [::] is reachable from the internet, including the database service that some plugin brought along. Compare the output with your start line:

./RustDedicated -batchmode -nographics \
  +server.port 28015 \
  +server.queryport 28017 \
  +server.identity "wipe" \
  +server.maxplayers 150 \
  +rcon.port 28016 \
  +rcon.web 1 \
  +rcon.password "YOUR-LONG-RANDOM-PASSWORD"

If your Rust installation came in through SteamCMD, the article Installing a game server with SteamCMD covers the layer underneath.

2. Leave open only the ports Rust really needs

Four ports, no more. RCON does not belong on the open internet, it belongs restricted to your own address, and Rust+ should only be opened if you actually use the companion app:

ufw allow 28015/udp comment "Rust game port"
ufw allow 28017/udp comment "Rust Query"
ufw allow from 203.0.113.10 to any port 28016 proto tcp comment "Rust RCON"
ufw allow 28082/tcp comment "Rust Companion"

Replace 203.0.113.10 with your own address. If your address changes regularly, the way to go is an SSH tunnel.

One warning that costs servers every year: the order in which you arm a firewall decides whether you lock yourself out. That order, including the way back in, is in the article Setting up the UFW firewall. If it happens anyway: on KVM root servers and dedicated servers from KernelHost you reach the machine through the VNC console in the customer panel. It does not hang off the network stack of the guest system, so no firewall rule inside the guest can block it.

3. Securing the query port without dropping off the server list

The obvious reflex, blocking the query port, is the most expensive mistake in this whole subject. Without it your server disappears from the server browser, reports wrong player counts and is listed as offline by the server list sites. You would have finished the attack yourself.

The right answer is a rate limit per source address. A real client browsing the list queries a few times per second, a reflection tool does it thousands of times. With nftables, in a table of its own that is evaluated before the filter chain:

table inet rust {
    chain input {
        type filter hook input priority -10; policy accept;
        udp dport 28017 meter rustquery { ip saddr limit rate over 15/second } drop
    }
}

You load the file with nft -f. The priority -10 makes sure the rule takes effect before the filter chain that UFW creates with priority 0. With classic iptables, the hashlimit module achieves the same thing:

iptables -A INPUT -p udp --dport 28017 -m hashlimit \
  --hashlimit-name rustquery --hashlimit-mode srcip \
  --hashlimit-above 15/sec --hashlimit-burst 30 -j DROP

Start generously and tighten the limit only once you have proof that legitimate queries get through. A limit that is too tight otherwise shows up on wipe day and nowhere else.

4. Taking load off connection tracking

This point gets overlooked almost every time, and it explains outages that look like a volumetric attack but are not one. For UDP traffic the kernel creates entries in connection tracking (conntrack), and with forged source addresses every new address means another entry. Once the table is full, the kernel drops packets without distinction: the attack and your players go out together. The system log then shows nf_conntrack: table full, dropping packet. You can check it like this:

sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
dmesg | grep -i conntrack

The most effective step is to keep the game traffic out of tracking in the first place. Rust does not need kernel state tracking for it, the server manages its sessions itself:

table inet raw {
    chain prerouting {
        type filter hook prerouting priority raw; policy accept;
        udp dport 28015 notrack
    }
}

With iptables the equivalent is:

iptables -t raw -A PREROUTING -p udp --dport 28015 -j NOTRACK

Only after that is it worth raising nf_conntrack_max. Enlarging the table first pushes the problem back by minutes and spends memory (RAM) to do it.

5. Receive buffers and kernel parameters

If packets arrive faster than the Rust process picks them up, the receive buffer of the socket overflows. To your players that looks like packet loss, even though the line is clear:

net.core.rmem_max = 16777216
net.core.rmem_default = 1048576
net.core.netdev_max_backlog = 16384

Put the values under /etc/sysctl.d/ and activate them with sysctl -p. Whether you need them at all is something the kernel tells you: if UdpRcvbufErrors in nstat -az is climbing, or if ss -lunp permanently shows something sitting in the receive queue, then they do their job. If both stay at zero, the change makes no difference. This is headroom, not protection.

6. Securing RCON

An open RCON port with a weak password is not a DDoS problem, it is a takeover: whoever has RCON can ban, unban and stop the game. Never leave rcon.password empty and never pick something guessable, a value from openssl rand -base64 32 takes five seconds to produce. And do not open the port to the public, restrict it to your own address.

7. Measures on the anti-cheat and plugin side

A sizeable share of the outages that operators report as DDoS are nothing of the sort. They are crashes that a single client triggers with a few hundred packets, because a hole is open in the server binary or in a plugin. What helps against that is maintenance, not bandwidth:

  • Keep the server binary up to date. The monthly update that forces the wipe is a security update at the same time. Putting it off leaves you sitting on the known bugs.
  • Keep the plugin framework up to date. Oxide/uMod and Carbon follow along after every Rust update. A framework that does not match the server version is the most common reason for crashes on wipe evening.
  • Fewer plugins. Every plugin is additional code in the same process. Plugins that bring their own web services (map views, statistics pages) open further ports, and in doing so they often publish exactly the address you are trying to protect.
  • Maintain your ban lists. Repeated connection attempts from the same account can be stopped with on-board means. Rust stores owners and moderators in server/<identity>/cfg/users.cfg and bans in server/<identity>/cfg/bans.cfg. A ban set through banid survives a restart.

Rust does not ship a whitelist in the core, it comes through the plugin framework. On a private or community server it works. On a public wipe server it is not an option: a server nobody can enter is just as empty as one that is offline.

8. The server list and your own address

There is nothing you can change about the public game server IP, but there is a lot you can change about everything next to it. Attackers often find the whole environment at once: the web server with the shop, the host of the Discord bot, the backup server, the panel access. Those addresses belong neither in the same announcement nor in old DNS records. Once a quarter, check which subdomain points where.

9. Logging, so you are not guessing during an attack

During an attack one question counts: how much is arriving, and on which port. Three commands are enough:

ip -s link show eth0
nstat -az | grep -i udp
journalctl -u rust-server -f

The first command shows packets, errors and drops per interface. Run it twice, ten seconds apart, and you have a rate instead of an absolute number. Your interface and your service unit may well be named differently, so check both with ip -br link and systemctl list-units --type=service. What the packets look like is shown by a sample, and that sample should stay short, because a capture under load costs CPU time:

tcpdump -ni eth0 -c 200 "udp port 28015"

Where self-protection ends

Now the honest part. Everything described so far only takes effect once the packets have arrived at your network card. A server typically sits on a 1 Gbps or 10 Gbps uplink. At the smallest possible packet size, a 1 Gbps line carries around 1.49 million packets per second and a 10 Gbps line around 14.88 million. That is the physical ceiling, regardless of CPU, kernel and firewall.

Set against that are real attacks. Two examples from live operations at KernelHost, both filtered in real time: a UDP flood against an ARK game server on port 7777/UDP with more than 112.2 Gbps and more than 8.7 million packets per second, and a multi-vector attack against a voice server on port 9987/UDP with more than 473.4 Gbps and more than 41.5 million packets per second.

Hold that against your own line: 473.4 Gbps is roughly 470 times a 1 Gbps connection, and still roughly 47 times a 10 Gbps connection. Your rule can be as correct as you like, it will never be executed, because the loss happens at the router in front of it. And long before the line is full, the CPU is finished: every packet costs an interrupt and one pass through the network stack, even when it is dropped afterwards.

That is why the two common emergency brakes are both unsatisfying. Null-routing (blackholing) takes the attacked IP out of the network and ends the attack, but it ends your server along with it. And a reactive diversion into a filtering system burns, in its switchover time, exactly the minutes in which the raid is decided. The only thing that works is filtering that runs permanently in the network in front of the server.

What KernelHost puts up against it

The always-on protection that runs on every server

DDoS protection at KernelHost is built in two layers and is permanently active, with nothing for you to switch on. The first layer is a global scrubbing network with 17 Tbps of mitigation capacity that intercepts volumetric attacks close to their source, before they reach the datacenter. The second layer is Arbor real-time filtering with 3.2 Tbps on site in Frankfurt am Main, which does the fine, protocol-level work and drops complex patterns on layers 3 through 7.

Two points are decisive. First, the filtering runs permanently, so there is no detection and switchover window in which your players get dropped. Second, no null-routing is used: the attacked IP stays in the network, only the malicious packets are discarded. The protection is included in every server package at no surcharge, with no separate protection package and nothing to set up. The servers are located in the maincubes Premium Datacenter in Frankfurt am Main (Germany), TÜV TIER3+ certified and directly connected to DE-CIX. The provider is KernelHost GmbH, based in Vienna (Austria). Which games and protocols are covered is listed in the article Game server DDoS protection in real time.

Advanced DDoS Protection for projects under constant fire

Some Rust projects are not hit now and then, they are targeted for weeks on end, with changing patterns and always right on wipe. For those cases there is Advanced DDoS Protection from €50.00 per month, PrePaid and with no minimum term. It brings three things the included always-on protection does not offer in that form:

  • A dedicated protected IP. Your server is switched over to it inside our own network, so nothing has to be rebuilt on your side.
  • Self-managed protection rules per port and protocol. In the customer panel you decide which port is filtered with which profile, for example 28015/UDP differently from the query port. Changes take effect in real time, with no ticket and no waiting.
  • A protection profile matched to the game in question. For Rust as well as for more than 40 further games, services and protocols, plus freely assignable TCP and UDP profiles for modified servers.

The PrePaid model applies here too: no minimum term, no notice period, no contract and no setup fee. Once the wave of attacks is over, you simply do not renew.

The two levels compared

Feature Included always-on protection Advanced DDoS Protection
Price Included in every server package, at no surcharge from €50.00 per month, PrePaid with no minimum term
Activation Active from the first minute, nothing to set up Order it, receive the protected IP, your server is switched over
IP address Server IP from the Frankfurt network Additional dedicated protected IP
Filtering 17 Tbps global scrubbing, plus 3.2 Tbps Arbor real-time filtering in Frankfurt am Main The same filtering, plus your own rules per port and protocol
Changing rules Maintained by KernelHost, fine-tuning by ticket By you in the customer panel, effective in real time
Game profiles More than 40 games and protocols Profile selectable per port, including for modified servers
Null-routing during an attack No No
Suitable for Every server, from the first wipe onwards Projects under constant, targeted fire

Common mistakes and how to fix them

The server has vanished from the server browser but keeps running: almost always the query port is blocked or rate-limited too tightly. Check with ss -lunp whether it is listening, and loosen the rate limit step by step. If Rust+ stays silent, app.port is usually closed.

All players have high ping and rubberbanding, but the line is not saturated: that points to packet rate rather than volume. Look at the dropped packets in ip -s link show and the UDP counters in nstat -az. A full receive buffer or exhausted connection tracking produces exactly this picture.

The firewall rule is correct and still has no effect: then the line in front of the server is saturated. A rule that is never executed, because the packet was already dropped at the router upstream, cannot achieve anything. From that point on, only filtering inside the network helps.

No more SSH access after arming the firewall: log in through the VNC console in the customer panel. From there you can switch the firewall off and add the missing rule, even when nothing works over the network any more.

The attack pauses after an IP change and comes back one or two days later: that is the normal case. Your server publishes the new address in the server list itself as soon as it is back online. An IP change buys hours, not a solution.

Admin commands you never issued are running on the server: that is not DDoS, that is a compromised RCON access. Change the password immediately, restrict the port to your own address, check the ban list.

If you are under attack right now

If your server already runs at KernelHost, the filtering is permanently active and there is nothing for you to switch on. If you still notice something unusual, open a support ticket so our team can readjust the filter rules for your IP. During an ongoing attack you can also reach us through the WhatsApp emergency chat at +43 650 8209883.

Give us four details right away: IP address, port, time window in your own time zone, and a short note on what you see (players getting dropped, server unreachable, high ping). That saves a round of follow-up questions, and those count when the wipe is running.

Frequently asked questions

My Rust server is unreachable right now: is this an attack?
Look at the network interface first. If the dropped packets in "ip -s link show" and the UDP errors in "nstat -az" climb sharply while the CPU load of the Rust process stays normal, that points to an attack. If both values stay calm and the process is gone, it was a crash.
Which ports does a Rust server need to have open?
28015/UDP for the game traffic, the query port (server.queryport, often 28017/UDP) for the server list, 28016/TCP for RCON, and 28082/TCP only if you use the Rust+ companion app. RCON belongs restricted to your own IP address, everything else stays closed.
Is it worth simply blocking the query port?
No, it does damage. Without a reachable query port your server disappears from the server browser and is listed as offline by the server list sites. What does work is a rate limit per source address, for example with an nftables meter or with the iptables module hashlimit.
Does changing the IP address help against an attack in progress?
Only briefly. Your server publishes the new address in the server list itself as soon as it is online again. In practice the attack comes back after one or two days. An IP change buys hours, it solves nothing.
Can I protect myself with a firewall on the server itself?
Against small attacks yes, against volumetric ones no. Your rules only run once the packets have arrived at the network card. A 1 Gbps line carries around 1.49 million small packets per second, and real attacks are many times above that. The loss then already happens at the router in front of it.
Does KernelHost take my IP offline during an attack?
No. Neither null-routing nor blackholing is used. The attacked IP stays in the network, only the malicious packets are discarded. The filtering runs permanently, so there is no switchover time at the start of an attack either.
What is included in the DDoS protection, and what does the Advanced tier cost?
The two-layer always-on protection is included in every server package at no surcharge: 17 Tbps of mitigation capacity in the global scrubbing network plus 3.2 Tbps of Arbor real-time filtering in Frankfurt am Main. Advanced DDoS Protection with a dedicated protected IP and your own rules per port starts at €50.00 per month, PrePaid with no minimum term and no setup fee.
What should I put in the ticket while the attack is running?
Four details are enough to start with: the affected IP address, the port, the time window in your own time zone, and a short note on what you see. That is enough to readjust the filter rules for your IP without a round of follow-up questions. In urgent cases you can also reach us through the WhatsApp emergency chat at +43 650 8209883.

Rust server Rust DDoS protection Game server protection UDP flood Query port nftables Advanced DDoS Protection Wipe