Protecting CS2 and Source servers from DDoS attacks

Published on 17 min read

With Counter-Strike 2 and the Source titles, game traffic and the server query run over the same port 27015. What you can secure yourself, and at what attack volume only filtering in the network in front of the server still helps.

A Counter-Strike server rarely goes down at a random moment. It goes down in the deciding round, shortly before the tournament final, or exactly when a banned player has been turned away for the second time. If you are under fire right now, you do not need a discussion of principles, you need an order of operations. This article starts with what you can change on the server yourself, then shows where those options end, and finally what has to happen in front of the server, in the network.

Why CS2 and Source servers are attacked so often

Counter-Strike is a game against the clock. A round lasts less than two minutes, a match takes barely an hour, and an outage inside that hour decides the result. The outage is therefore not merely annoying, it is a tool: whoever is behind gains time through an abort, and whoever runs a competing community knows that an evening full of timeouts sends the regulars elsewhere.

Then there is the way the engine is built. A Source server is publicly discoverable through its IP address and port, and that is a requirement, not an oversight: without an answered server query it appears in no browser. The question is therefore never whether an attacker finds your address, only what happens once someone starts firing at it.

The ports that matter

Counter-Strike 2, CS:GO and Garry's Mod share the same port logic, and inside it sits one detail that sets them apart from Minecraft or Rust:

  • 27015/UDP, game port and server query at the same time (-port). This single port carries the game traffic and, on top of that, the A2S query that Steam and every listing site use to read out the server. There is no separate query port here.
  • 27015/TCP, RCON. Same number, different protocol. Administration commands run over it, provided rcon_password is set.
  • 27020/UDP, GOTV or SourceTV (tv_port). Only needed if you actually broadcast.
  • 27005/UDP, client port. It originates from the player and needs no rule on the server.
  • With several instances the numbers count up (27016, 27017 as well as 27021, 27022 for GOTV). If the fast download for maps (sv_downloadurl) sits on the same host, 80/TCP or 443/TCP is added.

The shared port is the core of the problem. An A2S request is a packet of a few dozen bytes, the answer is a multiple of that, and with UDP the source address can be forged. An attacker can query other people's servers and steer the answers at the real target: your server is then not only a victim but an amplifier. Valve therefore put a challenge in front of A2S_INFO, which defuses the situation without ending it. How to tell that an attack is running is described in the article Detecting a DDoS attack on your server.

What you can do yourself before spending money

The following part costs nothing and is worth doing regardless of where your server runs. It does not take a volumetric attack off your hands, but it makes small and medium attacks fizzle out.

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 expected:

ss -lntup

Anything bound to 127.0.0.1 or ::1 needs no firewall rule. Anything listening on 0.0.0.0 or [::] is reachable from the internet, including the database that a statistics add-on brought along. Compare that with your start line:

./game/bin/linuxsteamrt64/cs2 -dedicated \
  -port 27015 \
  -maxplayers_override 12 \
  +game_alias competitive \
  +map de_dust2 \
  +sv_setsteamaccount YOUR_GSLT_TOKEN

On CS:GO and Garry's Mod, srcds_run does the same job. If the base is set up through SteamCMD, the article Installing a game server with SteamCMD helps.

2. Leave open only the ports the server really needs

Two UDP ports and one restricted TCP port, nothing more. RCON does not belong on the open internet:

ufw allow 27015/udp comment "CS2 game port and A2S"
ufw allow 27020/udp comment "GOTV"
ufw allow from 203.0.113.10 to any port 27015 proto tcp comment "RCON"

Replace 203.0.113.10 with your own address. If that address changes regularly, the way to go is an SSH tunnel rather than a permanent rule.

One note that costs servers every year: the order in which you arm a firewall decides whether you lock yourself out. It is described, together with the way back in, in the article Setting up the UFW firewall. If it does happen anyway: KVM root servers and dedicated servers from KernelHost have no IPMI and no iDRAC, so you reach the server through the VNC console in the customer panel. That console does not hang off the network stack of the guest system.

3. Rate limit the query traffic without falling out of the server list

This is where the most expensive mistake in this field sits. Because game traffic and server queries occupy the same port, the obvious reaction is the wrong one: blocking 27015/UDP or rate limiting it across the board throws out your own players in the same move and finishes the attack on the attacker's behalf.

The right place to start is the distinction between query packets and game packets. The engine sees the payload and brings three console variables for it:

sv_max_queries_sec 3
sv_max_queries_sec_global 60
sv_max_queries_window 30

The first limits the answered queries per source address, the second caps the total across all addresses, and the third sets the averaging window in seconds; find sv_max_queries shows whether your build knows them. They protect the CPU from producing answers for nothing, but they do not stop the packets from arriving.

One layer down, the query traffic can be separated cleanly. All connectionless packets of the Source engine, meaning server queries and connection setup, start with four set bytes (0xffffffff), while the traffic of players who are already connected does not carry that header. That is exactly what a rate limit with nftables can hook onto:

table inet cs2 {
    chain input {
        type filter hook input priority -10; policy accept;
        udp dport 27015 @th,64,32 0xffffffff \
            meter a2sflood { ip saddr limit rate over 10/second burst 20 packets } drop
    }
}

You load the file with nft -f. The priority -10 makes the rule take effect before the filter chain of UFW, and @th,64,32 reads the first four bytes behind the UDP header. With classic iptables, the same separation is achieved by matching on the A2S_INFO signature:

iptables -A INPUT -p udp --dport 27015 \
  -m string --algo bm --hex-string "|ffffffff54536f7572636520456e67696e6520517565727900|" \
  -m hashlimit --hashlimit-name a2sflood --hashlimit-mode srcip \
  --hashlimit-above 10/sec --hashlimit-burst 20 -j DROP

Start generously and only tighten the limit once legitimate queries are demonstrably getting through.

4. Secure RCON

An open RCON port with a weak password is not a DDoS problem, it is a takeover: whoever has RCON can change the map, ban every player and stop the server. Never leave rcon_password empty and never make it guessable, a value from openssl rand -base64 32 is enough. The Source titles also bring a brake against login attempts:

sv_rcon_minfailures 3
sv_rcon_maxfailures 5
sv_rcon_minfailuretime 30
sv_rcon_banpenalty 1440
sv_rcon_whitelist_address "203.0.113.10"

With that, an address is banned for a day after three failed attempts within 30 seconds, while your own address stays exempt; find sv_rcon shows which variables your build knows. The firewall restriction from step 2 is still the more effective one, because it does not let the attempt reach the application at all.

5. Take load off connection tracking

This point is often overlooked, and it explains outages that look like a volumetric attack but are none. The kernel creates entries in connection tracking (conntrack) for UDP traffic, and with forged source addresses every address means a new entry. Once the table is full, the kernel drops packets without distinction: the attack and your players go out together. The most effective step is to keep game traffic out of tracking in the first place, because the engine manages its sessions itself:

table inet raw {
    chain prerouting {
        type filter hook prerouting priority raw; policy accept;
        udp dport { 27015, 27020 } notrack
    }
    chain output {
        type filter hook output priority raw; policy accept;
        udp sport { 27015, 27020 } notrack
    }
}

With iptables the equivalent is iptables -t raw -A PREROUTING -p udp --dport 27015 -j NOTRACK and the same line for OUTPUT with --sport. After that the port needs an explicit rule of its own, because without tracking no rule that checks for an existing state applies any more.

6. Receive buffers and kernel parameters

If packets arrive faster than the server process picks them up, the receive buffer overflows. To the players that looks like packet loss even though the line is free. A drop-in file under /etc/sysctl.d/, activated with sysctl -p, buys room:

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

Whether the values are needed is something the kernel tells you itself: if UdpRcvbufErrors in nstat -az is rising, they are doing their job. If the counter stays at zero, the change does nothing. This is headroom, not protection.

7. Measures on the anti-cheat and plugin side

A considerable share of the outages reported as DDoS are none. They are crashes that a single client triggers with a few hundred packets, because a hole is open in the server binary or in an extension. No amount of bandwidth helps against that, only maintenance does:

  • Keep the server binary up to date. Updates close network bugs alongside game content. A server that trails two versions behind is wide open to known crash patterns.
  • Keep extensions matched to the engine version. For CS:GO and Garry's Mod, Metamod:Source and SourceMod are the usual base; for Counter-Strike 2, SourceMod is not yet available at the same maturity, and what is widely used there is Metamod:Source in its development builds together with CounterStrikeSharp. A mismatched extension is the most common reason for crashes after an update.
  • Fewer extensions. Every plugin is code in the same process, and extensions with web services of their own open further ports and often publish the very address you are trying to protect.
  • On Garry's Mod, limit the net messages. The best known own goal is a menu that listens on net.Receive without any limit: a client sends the message in a loop and slows the server down all by itself.
local last = {}

net.Receive("my_menu", function(len, ply)
    if last[ply] and CurTime() - last[ply] < 0.5 then return end
    last[ply] = CurTime()
end)

hook.Add("PlayerDisconnected", "my_menu_cleanup", function(ply)
    last[ply] = nil
end)

Also on Garry's Mod: sv_allowcslua 0 stops clients from running Lua code of their own. Bans belong in permanent storage, otherwise they are gone after the next restart: the Source titles have banid and writeid as well as addip and writeip for that; find ban shows what your build brings along.

8. Server list, whitelist and your own address

A public Counter-Strike server needs a Game Server Login Token, set through sv_setsteamaccount. Without that token it stays unregistered and turns up in no public list. For a fixed group that is exactly the right move: set sv_password, skip the registration and hand the address only to your own players. For a public server it is not an option: a server nobody can find is just as empty as one that is offline. The engine does not bring a real whitelist, that comes through extensions.

There is nothing you can change about the game server address, but there is plenty you can change about everything next to it. An attacker often finds the whole environment in one go, from the web server through the host of the Discord bot to the panel login. Those addresses do not belong in the same announcement as the server address, and not in old DNS records either.

9. Measure, so you do not have to guess during an attack

During an attack the most important question is: how much is arriving, and on which port. Three commands are enough:

ip -s link show eth0
nstat -az | grep -i udp
tcpdump -ni eth0 -c 200 "udp port 27015 and udp[8:4] = 0xffffffff"

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 value. The third line shows the connectionless packets only, meaning the class that a query flood abuses. Keep that capture short, because under load it costs CPU time of its own. If the counter fills up within seconds while barely anyone is connected, you have your answer.

Where self-protection ends

Now the honest part. Everything described so far only takes effect once the packets have arrived on your network card. With 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 operation at KernelHost, both filtered in real time: a UDP flood against a game server on 7777/UDP with over 112.2 Gbps and over 8.7 million packets per second, and a multi-vector attack against a voice server on 9987/UDP with over 473.4 Gbps and over 41.5 million packets per second.

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

That is why the two common emergency brakes are unsatisfying. Null-routing (blackholing) takes the attacked IP out of the network and does end the attack, but it ends your server as well. A reactive redirection costs, in its switchover time, exactly the minutes in which the match is decided. The only thing that works is filtering that runs permanently in the network in front of the server.

What KernelHost puts in front of it

The always-on protection that runs on every server

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

Two points make the difference. First, the filtering runs permanently, so there is no switchover time in which your players get dropped. Second, no null-routing is used: the attacked IP stays in the network, only the malicious packets are removed. 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), and 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 permanent fire

Some projects are attacked deliberately for weeks, with changing patterns and always exactly at match time. 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:

  • A dedicated protected IP. Your server is moved onto that address inside our 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 27015/UDP differently from 27020/UDP. Changes take effect in real time, without a ticket and without waiting.
  • A protection profile matched to the game in question. For Counter-Strike 2 and the Source titles as well as for over 40 further games and protocols, plus free TCP and UDP profiles for modified servers.

The PrePaid model applies here as well: 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 layers 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 moved over
IP address Server IP from the Frankfurt network An 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 rules of your own per port and protocol
Changing rules Maintained by KernelHost, fine-tuning by ticket Yourself in the customer panel, effective in real time
Game profiles Over 40 games and protocols Profile selectable per port, modified servers included
Null-routing during an attack No No
Suitable for Every server, from the first match onwards Projects under permanent and targeted fire

Common mistakes and their fixes

The server has disappeared from the browser but keeps running: usually 27015/UDP was blocked across the board or rate limited too tightly, and because game traffic and queries share the same port, a coarse rule hits both. Work with a match on the connectionless packets instead. If the server is missing although the port is reachable, check sv_setsteamaccount.

All players have high ping but the line is not full: 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. If the system log says nf_conntrack: table full, take the game port out with notrack.

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 in front, cannot achieve anything. From that point on, only filtering in the network helps.

No more SSH access after arming the firewall: log in through the VNC console in the customer panel (there is no IPMI and no iDRAC) and switch the firewall off from there.

The attack pauses after an IP change and returns one or two days later: that is the normal case, because your server publishes the new address itself as soon as it is registered again. An IP change buys hours, not a solution.

The server crashes reproducibly without the bandwidth standing out: usually not a DDoS, but a crash pattern in an extension or an outdated server version.

Administration commands from someone else are running on the server: not a DDoS, but a compromised RCON access. Change the password immediately and restrict the port.

If you are under attack right now

If your server already runs at KernelHost, the filtering is permanently active. 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 what you see (players getting dropped, server not in the browser, high ping). That saves a round of follow-up questions, and those count when a match is running.

Frequently asked questions

My CS2 server suddenly disappeared. Is this a DDoS attack?
Check first whether packets are arriving: the command ip -s link show, run twice ten seconds apart, gives you the rate, and nstat -az gives you the UDP counters. If both rise sharply while barely any players are connected, that points to an attack. If the process crashes instead without any unusual load, it is usually an extension or an outdated server version.
Can I simply block the query port?
No. On Counter-Strike 2, CS:GO and Garry's Mod the A2S query runs over the same port as the game traffic, usually 27015/UDP. Blocking that port or rate limiting it across the board throws out your own players as well and makes the server vanish from the server browser. The right approach is a limit that only hits the connectionless packets.
Which ports does a CS2 or Source server really need?
27015/UDP for game traffic and server queries, 27020/UDP for GOTV if you broadcast, and 27015/TCP for RCON, which should only be opened to your own address. The client port 27005/UDP originates from the player and needs no rule on the server.
Does changing the IP address help against the attack?
Only for a few hours. Your server publishes the new address itself as soon as it is registered again, and a game server without a public address has no players. Experience shows that the attack returns on the new address after one or two days.
Why does my firewall rule achieve nothing?
Because it only takes effect once the packet is already there. A 1 Gbps line carries around 1.49 million small packets per second, a 10 Gbps line around 14.88 million. If the attack is above that, the loss happens at the router in front and your rule is never executed. From that point on, only filtering in the network helps.
I locked myself out with the firewall. How do I get back in?
Through the VNC console in the customer panel. KVM root servers and dedicated servers from KernelHost have no IPMI and no iDRAC. The console does not hang off the network stack of the guest system, so a firewall rule inside the guest cannot block it.
Does KernelHost take my IP address offline during an attack?
No. No null-routing is used. The attacked IP stays in the network, only the malicious packets are removed. The protection is built in two layers: 17 Tbps of mitigation capacity in the global scrubbing network and 3.2 Tbps of Arbor real-time filtering on premise in Frankfurt am Main. It is included in every server package at no surcharge and is permanently active.
When do I need Advanced DDoS Protection?
When a project is attacked deliberately for weeks rather than occasionally, or when you want to control the protection rules per port yourself. It costs from €50.00 per month and comes with a dedicated protected IP plus rules per port and protocol that you manage yourself in the customer panel and that take effect in real time. PrePaid, with no minimum term, no notice period and no setup fee.

CS2 DDoS protection Counter-Strike 2 Source engine Garry's Mod A2S query Port 27015 Game server protection Advanced DDoS Protection