Protecting Counter-Strike 1.6 and GoldSrc servers from DDoS attacks
GoldSrc is more than twenty-five years old and answers connectionless queries with no rate limit of its own. Which ports a Counter-Strike 1.6 server really needs, what the query limiter in GoldSrc is actually called, why RCON runs over UDP there, and at what attack size only filtering in the network helps.
A Counter-Strike 1.6 server rarely goes down at a convenient moment. It goes down in the deciding round of a clan war, on a Friday night with a full server, or exactly when a banned player has been rejected for the third time. If you are being shot at right now, you do not need a discussion about networking theory, you need an order of operations. This article first shows how to protect a Counter-Strike 1.6 server from DDoS attacks while that is still possible with on-board tools, then where those options end physically, and finally what has to happen in the network in front of the server.
Everything here refers to a dedicated GoldSrc server (HLDS) on Debian 12, Debian 13, Ubuntu 22.04 LTS or Ubuntu 24.04 LTS. The commands are written for root; as a normal user, put sudo in front. One point up front, because it sets the order: do not change anything blindly during an ongoing attack, and do not restart the server before you have saved the measurements. After a restart they are gone, and without measurements every further step is guesswork.
Why Counter-Strike 1.6 servers are still attacked after more than twenty-five years
The scene is alive. Thousands of public GoldSrc servers still run in Eastern Europe, Turkey and Brazil, plus league and clan servers all across Europe. A server with 24 slots and a regular crowd is an evening of work per week for the operator and, for an attacker, a target that can be knocked offline for an hour for a few euros. The motive is the same as with any other game: whoever is behind gains time from an abort, and whoever runs a competing community knows that an evening full of timeouts makes the regulars move on.
The difference lies in the engine. GoldSrc comes from a time when UDP amplification was not a concern. The server answers connectionless queries readily, it has no rate limit that acts on the line, and it demands no challenge before the most expensive answer. That is not a reproach aimed at an engine from 1998, it is a starting position. And it leads to a statement you rarely read: a Counter-Strike 1.6 server needs more protection from outside today than a modern game server, not less. What a current title absorbs inside the engine has to be handled here by the network in front of it.
Then there is discoverability. A GoldSrc server is publicly listed with IP address and port, and that is a requirement, not an oversight: a server that answers no query appears in no server browser and on no listing site. So the question is never whether an attacker knows your address, only what happens when he shoots at it. What happens technically is explained in What is a DDoS attack?.
Which games run on GoldSrc and what already belongs to Source
GoldSrc is the original Half-Life engine, recognizable on the wire by protocol version 48. Source is its successor from 2004 and a separate, newer engine. The similarity of the names causes the most common mix-up in this field: Half-Life is GoldSrc, Half-Life 2: Deathmatch is Source. Both engines share the port logic and the idea of connectionless packets, but the console variables have different names, and remote control works over different protocols.
| Game | Engine | Does this article apply |
|---|---|---|
| Counter-Strike 1.6 | GoldSrc | yes, in full |
| Counter-Strike: Condition Zero | GoldSrc | yes, in full |
| Half-Life 1 (Deathmatch, Opposing Force, Blue Shift) | GoldSrc | yes, in full |
| Day of Defeat 1.3, Team Fortress Classic, Ricochet, Deathmatch Classic | GoldSrc | yes, in full |
| Sven Co-op | its own GoldSrc branch | yes, with one deviation for RCON |
| Half-Life 2: Deathmatch, Counter-Strike: Source, Day of Defeat: Source | Source | no, see CS2 and Source |
| Team Fortress 2 | Source | no, see TF2 |
| Left 4 Dead 2 | Source | no, see Left 4 Dead 2 |
| Garry's Mod | Source | no, see Garry's Mod |
Keep two layers apart while reading. The sections about ports, about connectionless packets and about rules in the kernel (nftables, iptables, sysctl) apply to both engines, because the kernel only ever sees UDP packets. The sections about console variables, about RCON and about the server binary apply to GoldSrc only. Anyone who carries a Source guide over to a 1.6 server writes variables into server.cfg that do not exist there, and then wonders why nothing happens.
The ports a GoldSrc server actually uses
A GoldSrc server uses exactly one UDP port for everything that makes up the game, plus a second one for signing in with Steam. The game port is set with -port, the matching console variable is simply called port and defaults to 27015. The Steam port is set with -sport and defaults to 26900:
./hlds_run -game cstrike -console \
-port 27015 \
-sport 26900 \
+ip 203.0.113.10 \
+maxplayers 24 \
+map de_dust2
| Port and protocol | What for | Has to be open from outside |
|---|---|---|
| 27015/UDP | game traffic, server query and RCON, all on the same port | yes, without this port there is no game |
| 26900/UDP | Steam and VAC port of the server process (-sport), counts up per additional instance |
no, outbound to Steam only |
| 27005/UDP | client port, originates from the player | no, needs no rule on the server |
| 27020/UDP | HLTV proxy, a separate process next to the game server | only if you actually broadcast |
| 27016, 27017 and following | further game instances on the same host | per instance individually, not as a range |
| 26901, 26902 and following | Steam port of the further instances | no, outbound only |
| 27015/TCP | Sven Co-op only, whose manual lists this port for RCON | no, for your own address only |
| 80/TCP and 443/TCP | fast download for maps and sounds (sv_downloadurl), if hosted on the same machine |
only if the web server runs there |
| 22/TCP | SSH access | no, restrict to your own address |
The first row holds the most important difference from the Source engine, and it regularly costs an evening of troubleshooting. On Counter-Strike 1.6, remote control does not run over TCP but over connectionless UDP packets on the same game port 27015. The server binary opens no TCP port at all. Anyone who copies the line for 27015/TCP out of a Source guide creates a firewall rule that points at nothing and then believes RCON is secured. The only exception is Sven Co-op, which maintains its own branch of the engine and lists 27015/TCP for RCON in its own manual.
The second peculiarity concerns 26900/UDP. Over this port the server process signs in with Steam and keeps its link to the master server alive. It does not have to be reachable from outside, but it does have to work outbound. If the port is taken, the process picks the next free number by itself, which is why a host with four instances ends up with 26900, 26901, 26902 and 26903. Filtering outbound traffic with a broad brush costs you the listing while the server keeps running at full speed.
Connectionless queries: why a GoldSrc server answers so readily
A connectionless packet is a UDP packet whose first four bytes are all set to 0xFF. The engine checks exactly that and nothing else: if those bytes read FF FF FF FF, the packet counts as connectionless and is evaluated immediately, without any session, player or prior sign-in having to exist. If they read anything else, the engine looks for a matching source address among the already connected players and drops the packet when it finds none.
Behind those four bytes comes either a single marker byte or a word in plain text. That split is the anchor point for every sensible rate limit, because traffic from a connected player never carries this header.
| Query | Marker after the four bytes | What the server does | Size ratio |
|---|---|---|---|
| A2S_INFO | 0x54 plus the string "Source Engine Query" |
sends server name, map, player count, version and tags | request 25 bytes, answer usually 100 to 300 bytes |
| A2S_PLAYER | 0x55 |
sends the player list with names, scores and time played | answer grows with every connected player |
| A2S_RULES | 0x56 |
sends every console variable flagged as a server variable, with its value | several kilobytes on a server with many extensions |
| getchallenge | plain text getchallenge |
issues a challenge for the connection handshake | request around 17 bytes, answer around 40 bytes |
| challenge rcon | plain text challenge rcon |
issues a challenge for remote control | small on both sides |
| rcon | plain text rcon |
runs a command after checking challenge and password | the answer is the full console output |
| ping | 0x69 or plain text ping |
acknowledges with a marker byte | tiny on both sides |
Why the answer is bigger than the request
An A2S_INFO request is exactly 25 bytes long: four bytes FF FF FF FF, one byte 0x54 and the 20 byte string "Source Engine Query" with a trailing null. The answer contains server name, map name, folder, game description, player count, maximum, bot count, server type, operating system, password flag, VAC status and version. On any normally named server that is a multiple of the request. The US agency CISA puts the amplification factor of the Steam protocol at 5.5 in Alert TA14-017A.
The richest vector, though, is not A2S_INFO but A2S_RULES. That query returns every console variable flagged as a server variable, name and value in text form. On a well-kept Counter-Strike 1.6 server with extensions that quickly means several hundred entries, and the answer grows to several kilobytes accordingly. Out of a 25 byte request you then get far more than a factor of 5.5. That is why GoldSrc servers have been listed by amplification services for years.
What spoofed source addresses turn this into
UDP has no handshake. So there is no point in the protocol at which a sender would have to prove that he is really reachable at the address he claims. An attacker therefore sends queries to thousands of foreign GoldSrc servers and writes the address of his actual victim into the source field. Every one of those servers answers dutifully, and all answers pile up at the victim. Your own server is not being attacked in that scenario, it is being used as a tool against a third party.
Three practical consequences follow. First, your log shows queries from addresses that never enter a game. Second, your server produces outbound traffic that you may well be paying for. Third, your address ends up on the lists such services maintain and is queried permanently from then on. Filtering that works in front of the server solves both directions at once: it keeps the flood away from you and stops your server from taking part in somebody else's attack.
A word on the countermeasure the engine does offer: the console variable sv_enableoldqueries defaults to 0. In that position the server no longer answers the old pre-Steam queries and instead demands the modern format with a valid marker. Do not set this variable to 1. It exists for tools that have not been maintained for a decade and a half, and it turns your server back into the amplifier it was before the change.
The attack types that actually hit a GoldSrc server
Query flood on 27015/UDP
The query flood is the normal case. An attacker sends A2S_INFO, A2S_PLAYER and A2S_RULES in quick succession at your game port, often from many sources at once. Every single request is harmless, the sum is not: the server process has to read, match and answer every packet, and it does so in the same loop that also computes the game. On GoldSrc that is particularly sensitive, because the game server runs as a single process and effectively uses one CPU core. Players notice it as rising ping and stutter long before the line is full.
Reflection off foreign and off your own servers
Reflection is the case from the previous section seen from the other side. If your server is picked as the target, the answers of hundreds of foreign game servers arrive at your address. Those packets come from real, clean networks, they are protocol compliant, and by source address they cannot be told apart from legitimate traffic. That is exactly why reflection is so unpleasant for a firewall on the server: there is no address list you could block without blocking half of Europe along with it.
Join flood over getchallenge and connect
The connection handshake on GoldSrc runs in two steps. The client sends getchallenge, the server issues a challenge and remembers it together with the source address, then the client sends connect with that challenge. The table for those challenges holds 1024 entries in the unmodified engine, and the source code carries a remarkable comment from Valve itself: the table was deliberately made large to prevent an attack from cycling all entries out before legitimate users could connect.
That is an honest description of the problem and at the same time proof that it is known. A join flood from spoofed addresses fills the table, and the legitimate challenge of a real player gets overwritten in the process. The player then sees "No challenge for your address" and cannot get in although the server is running and slots are free. Maintained rebuilds of the engine solve this differently: they compute the challenge from the address and a random value instead of storing it in a table, and therefore have nothing left that could overflow.
RCON brute force over the game port
Because RCON on GoldSrc runs over connectionless UDP packets on the game port, an attacker can simply send password attempts along while the players keep playing. The sequence is always the same: request challenge rcon, receive the challenge, then send rcon with challenge, password and command. The server checks the challenge first, the password second, and logs every failed attempt as "Bad Rcon".
Two things help more here than they look at first. First, the challenge has to come back, so the attacker cannot spoof his address: an address restriction really does work. Second, the engine counts failed attempts, but only in a table with 32 slots. An attacker probing from many addresses at once pushes entries out faster than the ban logic can act. The reliable answer is therefore not counting, it is restriction.
Flood against the server listing
This case is almost always misread, because it does not look like an attack. The server runs, the players already inside stay inside, but the server disappears from the server browser and nobody new gets in. The cause is that the process can no longer keep its Steam sign-in alive over 26900/UDP, either because outbound traffic drowns in the load or because the process is busy answering queries. For the operator this is the most expensive state of all: the server keeps costing money but no longer fills up.
Load spikes that are not attacks at all
A substantial share of outages reported as DDoS are not. They are crashes and load spikes triggered by a single client with a few hundred packets, because a limit is missing in the server binary or in an extension. The traffic stays tiny and the server bows out anyway. Bandwidth does not help against that. How to tell the two cases apart is covered in Detect a DDoS attack on your server.
How to tell an attack is running
Five measuring points are enough, and three of them are in the server console. Start there, because you can read those values even when your SSH session is sluggish.
The server frame rate. The command stats in the server console prints a line with the columns CPU, In, Out, Uptime, Users, FPS and Players. The FPS column is the frame rate of the server and should sit near the value that sys_ticrate sets (default 100). If it falls below half while the player count is normal, the process is working on something other than the game. The In and Out columns show throughput in kilobytes per second and immediately reveal whether far more is arriving than leaving.
The player list. status lists every connected player with address, ping and packet loss. If loss rises for everybody at once, the cause is the network and not individual connections. If the list is short and the frame rate is still on the floor, that confirms the suspicion of a query flood.
The server log. With log on the server writes into the directory that logsdir defines (default logs). Two patterns matter there: repeated lines containing "Bad Rcon" and, once you set sv_logblocks 1, lines of the form "Traffic from ... was blocked for exceeding rate limits". The second line is direct proof that the built-in query limiter is currently working.
The listening ports. ss -lnup shows which UDP services really listen. On a clean GoldSrc host that is the game port and the Steam port, nothing else. Everything beyond that deserves a check.
The packet rate. At system level three commands say everything that counts in the first few minutes:
ip -s link show eth0
nstat -az | grep -i udp
tcpdump -ni eth0 -c 200 "udp port 27015 and udp[8:4] = 0xffffffff"
Run the first command twice, ten seconds apart, and you have a rate instead of an absolute value. The third line shows only the connectionless packets, which is exactly the class a query flood abuses. If the counter reaches 200 within seconds while barely anybody is connected, you have your answer. Keep the capture short, because under load it costs processing time of its own.
What you can do yourself before spending money
The following part costs nothing and pays off regardless of where your server is hosted. It will not take a volumetric attack off your hands, but it makes small and medium attacks fizzle out, and it removes the outages that get reported as DDoS attacks by mistake.
1. Take inventory: what is really listening
Before you write a rule, work out which services are reachable. On a 1.6 host that has grown over time there are almost always more than expected, because next to HLDS there is often a web server for the map files, a statistics database and a second server for clan wars:
ss -lntup
Anything bound to 127.0.0.1 or ::1 needs no rule. Anything listening on 0.0.0.0 or [::] is reachable from the internet, including the database that a statistics module brought along. The attacker's view comes from a port scan from outside, and experience says it differs from your own expectation:
nmap -Pn -sU -p 26900-26910,27015-27020 YOUR.SERVER.IP.ADDRESS
nmap -Pn -sT -p 22,80,443,3306,27015 YOUR.SERVER.IP.ADDRESS
The second command is the interesting one. If it finds an open port on 27015/TCP, that is not your Counter-Strike server, because HLDS opens no TCP port. If the base is freshly set up or you want to retrace it, Install a game server with SteamCMD describes the path from SteamCMD to a running service.
2. Leave open only the ports HLDS really needs
One UDP port to the outside, the game needs no more. The Steam port only has to work outbound:
ufw allow 27015/udp comment "CS 1.6 game port, query and RCON"
ufw allow from 203.0.113.10 to any port 22 proto tcp comment "SSH"
ufw default deny incoming
ufw default allow outgoing
ufw --force enable
Replace 203.0.113.10 with your own address. And note what is not in there: no line for RCON, because RCON uses the same port as the game. On GoldSrc you cannot separate remote control through the port rule, only through packet content. Step 5 takes care of that.
The order in which you arm a firewall decides whether you lock yourself out; it is written down, including the way back, in Set up the UFW firewall without locking yourself out. If it happens anyway: KVM root servers and dedicated servers from KernelHost have no IPMI and no iDRAC, you reach the server through the VNC console in the customer panel, and that console does not depend on the guest system's network stack.
3. Set the built-in query limiter correctly
Here is the mistake that makes up the due diligence of this article. GoldSrc does have a built-in limit for connectionless packets, but the variables are named differently than in the Source engine: without the sv_ prefix. Writing the Source names into a server.cfg for Counter-Strike 1.6 silently creates new, ineffective variables.
| Purpose | Name in GoldSrc | Default | Name in the Source engine |
|---|---|---|---|
| queries answered per source address | max_queries_sec |
3.0 | sv_max_queries_sec |
| queries answered across all addresses | max_queries_sec_global |
30 | sv_max_queries_sec_global |
| averaging window in seconds | max_queries_window |
60 | sv_max_queries_window |
| log blocked senders | sv_logblocks |
0 | no counterpart |
The mechanism is simple and worth knowing, because it explains why a setting that is too strict does harm. For every incoming packet with the header FF FF FF FF the engine first increments the counter of the source address and divides it by max_queries_window. If the result is above max_queries_sec, the packet is dropped before it is evaluated at all. The same calculation then runs across all addresses together against max_queries_sec_global. A workable starting point for a public server:
max_queries_sec 2
max_queries_sec_global 40
max_queries_window 30
sv_logblocks 1
sv_enableoldqueries 0
Two limitations belong with this, and both tend to be left unsaid. First, the engine only remembers a limited number of source addresses at a time, on the order of a few hundred entries that expire after two minutes of silence. During an attack with spoofed senders that table is full within fractions of a second and then simply cycles. So the limiter protects your processing time, not your line: the packets have long since arrived. Second, the queries of listing sites and of the Steam master server count towards the same budget. A global value that is too low drops you out of the server list, and for a public server that is the same thing as a successful attack. Start generously and tighten only once sv_logblocks 1 shows that the right addresses are affected.
4. Limit connectionless packets in the kernel
One layer down the same traffic can be separated before the server process ever sees it. The anchor point is exactly that header of four set bytes, because traffic from already connected players does not carry it. With nftables it looks like this:
table inet goldsrc {
chain input {
type filter hook input priority -10; policy accept;
udp dport 27015 @th,64,32 0xffffffff @th,96,8 0x54 \
meter kh_a2s { ip saddr limit rate over 5/second burst 10 packets } drop
udp dport 27015 @th,64,32 0xffffffff \
meter kh_connless { ip saddr limit rate over 20/second burst 40 packets } drop
}
}
Load the file with nft -f. The priority -10 makes sure the rule takes effect before the UFW filter chain. @th,64,32 reads the first four bytes behind the UDP header, @th,96,8 reads the fifth byte, which is the marker for the query type. The first rule therefore limits A2S_INFO specifically to five requests per second and address, the second catches everything else connectionless at twenty requests per second. Neither rule touches the game traffic of your players.
With classic iptables, a match on the A2S_INFO marker achieves the same:
iptables -A INPUT -p udp --dport 27015 \
-m string --algo bm --hex-string "|ffffffff54536f7572636520456e67696e6520517565727900|" \
-m hashlimit --hashlimit-name a2sflood --hashlimit-mode srcip \
--hashlimit-above 5/sec --hashlimit-burst 10 -j DROP
After every change, check that your server is still in the server browser, and do it from a foreign connection, not from the server itself. A rule that costs you the listing has finished the attack on the attacker's behalf.
5. Switch RCON off or restrict it to your own address
An open RCON access with a weak password is not a DDoS problem, it is a takeover: whoever has RCON changes the map, bans every player and stops the server. The cleanest solution is the radical one: leave rcon_password empty if you do not need remote control. The server then answers every attempt with "No password set for this server" and runs nothing. You can still administer it through the server console, through screen or through your panel.
If you do need RCON, use a long random value together with the brakes the engine ships with. All four variables exist in GoldSrc, and the defaults in brackets are remarkably generous:
rcon_password "PUT_A_LONG_RANDOM_VALUE_HERE"
sv_rcon_minfailures 3
sv_rcon_maxfailures 5
sv_rcon_minfailuretime 30
sv_rcon_banpenalty 1440
sv_rcon_minfailures (default 5) is the number of failed attempts within sv_rcon_minfailuretime (default 30 seconds) that leads to a ban. sv_rcon_maxfailures (default 10) is the absolute ceiling. sv_rcon_banpenalty is the ban duration in minutes, and there is a trap here: the default is 0, and 0 means a ban without expiry in the underlying addip. Mistype your own password often enough and you lock yourself out permanently. A value such as 1440 (one day) is the better practical choice. Generate a password with openssl rand -base64 32.
Because RCON sits on the game port, it cannot be restricted through the port rule, only through packet content. If you do not use remote control at all, drop the two relevant plain text words directly in the kernel:
table inet goldsrc_rcon {
chain input {
type filter hook input priority -11; policy accept;
udp dport 27015 @th,64,32 0xffffffff @th,96,32 0x72636f6e drop
udp dport 27015 @th,64,32 0xffffffff @th,96,32 0x6368616c drop
}
}
The first rule matches packets that start with rcon after the header, the second those starting with chal, which is the challenge request for RCON. If RCON should work from your own address only, put ip saddr != 203.0.113.10 in front of each of the two lines. The connection handshake of your players uses getchallenge and is unaffected by either rule.
A note on maintained rebuilds of the engine: they ship their own allow list, managed through the console commands rcon_adduser with an IP address or a CIDR range, rcon_deluser and rcon_users. That is more convenient than a firewall rule, but it does not replace one, because the attempt still travels all the way to the application.
6. Old binaries: what actually hangs on them
The question comes up in every discussion, and it usually gets an answer that is too sharp. The factual picture: the unmodified Valve server binary works, but it has received little maintenance for years, and it carries exactly the limits that were considered sufficient in 2005. Next to it stands ReHLDS, an open source rebuild of the HLDS engine that fixes bugs and adds further limits; the most recently published build carries the number 3.15.0.896 and dates from May 2026. For the game library there is a matching counterpart in ReGameDLL_CS, for extensions there is Metamod-r and, on top of it, AMX Mod X, most recently published as version 1.9.0.5303 in April 2026.
The honest assessment reads: running an old, unmodified binary does not mean an open hole, it means less built-in resistance. The difference is not one single flaw but a set of limits that a maintained build brings along and the unmodified one does not know:
- Simultaneous connections per address. The variable
sv_rehlds_maxclients_from_single_ip(default 5) caps how many connections from the same address can be built up at once. Against a join flood from a handful of sources this is the single most effective setting. - Command rates per player. The groups
sv_rehlds_movecmdrate_max_avg(default 400) andsv_rehlds_stringcmdrate_max_avg(default 80) limit how many movement and string commands a single client may send, each with a value for short bursts and a ban duration in minutes. That catches exactly the cases where one client slows the whole server down. - File requests.
sv_rehlds_dlfile_refillrate(default 50) and the matching values limit how quickly a client may request files. - Challenge without a table. Instead of managing 1024 slots, the challenge is computed from the address and a random value. There is then nothing left for a join flood to overflow.
- Limits when unpacking incoming data. A set of values caps the ratio and the size of compressed payloads a client sends.
Which build is running on your machine is answered by the command version in the server console. Two things still need saying. First, switching engines is not an emergency tool: it belongs planned, with a backup and without an ongoing attack, and the extensions have to match the build you choose. Second, and more important: do not switch off a check in order to make an error go away. If a map only starts once the consistency check is off, the map is the problem, not the check. Every disabled check is a door that stays open afterwards, and as a rule nobody notices until it gets used.
7. Cap slots, rates and downloads
GoldSrc allows at most 32 slots per server, and every occupied slot costs bandwidth and processing time. Bandwidth per player is governed by a ceiling that is not set out of the box: sv_maxrate is 0, meaning unlimited up to the technical ceiling of 100,000 bytes per second per client. Do the math: 24 players times 100,000 bytes is 2.4 megabytes per second outbound alone, roughly 19 Mbps, and that is without any attack. A realistic cap:
sv_maxrate 25000
sv_minrate 5000
sv_maxupdaterate 66
sv_minupdaterate 20
sv_timeout 45
sys_ticrate 100
sv_maxupdaterate (default 30) limits how many updates a client receives per second; higher values cost bandwidth and processing time in equal measure. sv_timeout (default 60) determines how long a silent client keeps its slot, and a shorter value frees blocked slots faster. sys_ticrate (default 100) is the frame rate the server aims for and therefore the ceiling of what the FPS column in stats can show. Much higher values are common in the scene; they cost processing time, and that is exactly what you run out of under a query flood.
For files the same rule applies as with every Valve title: whatever the game port delivers, you pay for twice, once in bandwidth and once in processing time inside the same process. Put maps and sounds on a web server and switch off what you do not need:
sv_downloadurl "https://cdn.example.org/cstrike/"
sv_allowdownload 1
sv_allowupload 0
sv_send_logos 0
mp_consistency 1
sv_allowupload defaults to 1 and lets clients upload their own spray logos. That is a data source controlled by the client, and it adds no gameplay value whatsoever. sv_send_logos 0 additionally stops the server from redistributing those images to everybody else. mp_consistency defaults to 1 and belongs left there.
The engine also ships its own block lists: addip, removeip, listip and writeip for addresses, banid, removeid, listid and writeid for player IDs, governed by sv_filterban (default 1) and logged with sv_logbans 1. What matters is what these lists do and do not achieve: they stop somebody from playing. They do not stop his packets from arriving, because the check runs inside the server process after the kernel has already delivered the packet. Against a troublesome player banid is the right tool, against a packet flood it does nothing. Do not forget the writeip or writeid, otherwise the bans are gone after the next restart.
8. Relieve connection tracking and the receive buffer
This point gets overlooked often and explains outages that look like a volumetric attack but are none. The kernel creates connection tracking (conntrack) entries for UDP traffic, and with spoofed source addresses every address means a new entry. Once the table is full the kernel drops packets indiscriminately: the attack and your players get thrown out together. Current state and ceiling are one look away:
sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
The most effective step is to keep the game traffic out of tracking altogether, because the engine manages its sessions itself:
table inet raw {
chain prerouting {
type filter hook prerouting priority raw; policy accept;
udp dport { 27015, 26900 } notrack
}
chain output {
type filter hook output priority raw; policy accept;
udp sport { 27015, 26900 } 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. The port then needs an explicit allow rule, because without tracking no rule that checks for an existing state applies any more. If packets arrive faster than the server process picks them up, the receive buffer overflows on top of that, and for the players it looks like packet loss on a free line:
net.core.rmem_max = 16777216
net.core.rmem_default = 1048576
net.core.netdev_max_backlog = 16384
Put the file under /etc/sysctl.d/ and activate it with sysctl -p. Whether the values are needed at all is something the kernel tells you: if UdpRcvbufErrors in nstat -az is rising, they help. If the counter stays at zero, the change does nothing. This is headroom, not protection.
9. Record a baseline while everything is normal
The most important step is the one almost nobody takes in advance. On a quiet evening, write down the output of stats with a full server, the packet rate from ip -s link show and the number of queries per minute from the log. Without that baseline you cannot say after an incident whether 40,000 packets per second was a lot or simply a Friday night on a busy server. With the baseline the judgment takes two minutes, and that is exactly what counts while a match is running.
Where these measures end
Now the honest part. Everything described so far only takes effect once the packets have arrived on your network card. A firewall rule decides about a packet that has already traveled down the wire. You can drop it, but you cannot un-send it.
Do the math once. A typical game server sits on 1 Gbps, which is 125 megabytes per second. At the smallest possible packet size that line carries around 1.49 million packets per second, a 10 Gbps line around 14.88 million. That is the physical ceiling, independent of CPU, kernel and firewall. A normal server kernel handles a few hundred thousand packets per second depending on processor and network card before it starts dropping.
On GoldSrc one peculiarity moves that point even closer. The game server runs as a single process and effectively uses one CPU core. Every connectionless packet is handled in the same loop that also computes the game. An attack that does not even fill a tenth of your line can therefore halve the frame rate of your server. Operators experience this as "utilization was not even high and everything was gone anyway". That is exactly why the FPS column in stats is the single most informative number you have.
Against that stand real attacks. Two examples from operations at KernelHost, both filtered in real time: a UDP flood against a game server on 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 9987/UDP with more than 473.4 Gbps and more than 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.
This is why the two common emergency brakes are unsatisfying. Null routing (blackholing) takes the attacked IP address off the network and does end the attack, but it ends your server too: for your players the result is identical to a successful attack. A reactive redirect costs, during its switchover window, exactly the minutes in which the clan war 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 included with every server package
DDoS protection at KernelHost has two layers and is permanently active, without you switching on, ordering or configuring anything:
- Layer 1: 17 Tbps of mitigation capacity in the global scrubbing network. Volumetric attacks are cleaned close to their source, long before they reach the data center.
- Layer 2: Arbor real-time filtering with 3.2 Tbps in Frankfurt am Main. Directly in front of the server, protocol specific patterns on layers 3 to 7 are recognized and dropped, packet by packet.
Two properties are decisive. First, the filtering runs permanently, so there is no switchover window in which your players get thrown out. Second, no null routing is used: the attacked IP address stays on the network and only the malicious packets are dropped. The protection is included in every server package at no surcharge, with no separate protection product and no setup, and it is active from provisioning onwards. The servers are in the maincubes Premium Datacenter in Frankfurt am Main. Which games and protocols are covered is listed in Game server DDoS protection in real time, and how the filtering works for game protocols is described in Specialized game DDoS protection.
Advanced DDoS Protection for projects under permanent fire
Some projects are attacked not occasionally but deliberately and for weeks, with changing patterns and always exactly at the agreed match time. For those cases there is Advanced DDoS Protection from €50.00 per month, PrePaid and with no minimum term. The difference is not more capacity, it is control:
- A dedicated protected IP. Your server is switched over to that address inside our network, no rebuild is needed on your side.
- Self-managed protection rules per port and protocol. In the customer panel you decide which port is filtered with which profile, so 27015/UDP differently from the web server that delivers your maps.
- Changes take effect in real time, with no ticket and no waiting. So you can fine-tune while an attack is running.
- A protection profile matching the game. Counter-Strike 1.6, Sven Co-op and Half-Life 2: Deathmatch are on the list alongside more than 40 further games and protocols, plus free TCP and UDP profiles for servers with unusual configurations.
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 tiers compared
| Feature | Included always-on protection | Advanced DDoS Protection |
|---|---|---|
| Price | included with every server package, no surcharge | from €50.00 per month, PrePaid with no minimum term |
| Activation | active from provisioning onwards, nothing to set up | order it, receive the protected IP, server is switched over |
| Filter capacity | 17 Tbps of global scrubbing plus 3.2 Tbps Arbor real-time filtering in Frankfurt am Main | the same two-layer filtering, plus your own rules |
| IP address | the IP address of your server | an additional dedicated protected IP |
| Changing rules | maintained by KernelHost, fine-tuning by ticket | yourself in the customer panel, effective in real time |
| Game profiles | more than 40 games and protocols, GoldSrc titles included | profile selectable per port, also for unusual configurations |
| Null routing during an attack | no | no |
| Fits | every server, from the first match onwards | projects under permanent, targeted fire |
For most Counter-Strike 1.6 projects the included always-on protection together with a clean server configuration is enough. Advanced DDoS Protection is the answer to somebody taking it personally.
Common mistakes and how to fix them
The rule for 27015/TCP does nothing: correct, it cannot do anything. HLDS opens no TCP port; on GoldSrc, RCON runs over connectionless UDP packets on the game port. That line comes from a Source guide. Work with the content match from step 5 instead, or simply leave rcon_password empty.
sv_max_queries_sec is in the server.cfg and changes nothing: that name belongs to the Source engine. In GoldSrc the variables are called max_queries_sec, max_queries_sec_global and max_queries_window, all without the sv_ prefix. The Source spelling silently creates a new, unused variable, which is also why there is no error message.
The server has vanished from the server browser but keeps running: check outbound traffic on 26900/UDP first, because that is how the process keeps its Steam sign-in alive. Then check whether sv_lan is set to 1 by mistake or the server was started with -nomaster. Third possibility: max_queries_sec_global is too low and the queries of the listing sites are being dropped along with everything else.
Every player has high ping but the line is not full: that points to packet rate rather than volume. Look at the FPS column in stats, at the dropped packets in ip -s link show and at the UDP counters in nstat -az. If the system log says nf_conntrack: table full, take the game port and the Steam port out with notrack.
New players cannot get in and see "No challenge for your address": that is the signature of a join flood. The challenge table of the unmodified engine holds 1024 entries, and a flood from spoofed addresses pushes out the real player's challenge before he can send his connect. Limit connectionless packets in the kernel and consider a maintained build of the engine that computes the challenge instead of storing it.
The log keeps showing lines with "Bad Rcon": somebody is trying passwords. That is not a volumetric attack, it is a takeover attempt. Change the password, set sv_rcon_banpenalty to a value above zero and restrict RCON to your own address with the rule from step 5.
The firewall rule is correct and still has no effect: check with nft list ruleset or iptables -L INPUT -n -v whether the hit counters are rising. If they stay at zero, the rule is never reached, because it sits behind the UFW chains or was lost during the last reboot. If they rise and nothing changes, the line in front of the server is saturated, and from there on only filtering in the network helps.
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, and a forgotten DNS record or a bot with a status display does the rest. An IP change buys hours, not a solution.
The server crashes reproducibly without bandwidth standing out: usually not a DDoS attack but a crash pattern in an extension, or a server binary that does not match the modules loaded on top of it. Bandwidth does not help here; a matching combination of engine, game library and extensions does.
In brief
- A Counter-Strike 1.6 server needs exactly one port open to the outside: 27015/UDP. Game traffic, server query and RCON share it, and GoldSrc has no separate query port.
- RCON on GoldSrc runs over connectionless UDP packets on the game port. The server binary opens no TCP port, so a rule for 27015/TCP points at nothing. The exception is Sven Co-op, whose manual lists 27015/TCP for RCON.
- The built-in query limiter in GoldSrc is called
max_queries_sec,max_queries_sec_globalandmax_queries_window, without thesv_prefix of the Source engine. Defaults are 3.0, 30 and 60. - Connectionless packets start with four bytes of
0xFF. That is exactly where the rate limit belongs in the kernel, because traffic from connected players lacks this header and stays untouched. - An A2S_INFO request is 25 bytes long and the answer a multiple of that; CISA puts the amplification factor of the Steam protocol at 5.5. A2S_RULES is higher still, because its answer grows with the number of server variables.
sv_enableoldqueriesbelongs at 0. At 1 the server again answers the old queries without checks and becomes an amplifier aimed at third parties.- With 64 byte packets a 1 Gbps line carries around 1.49 million packets per second. Above that, only the network in front of the server decides, never a setting on the server itself.
- At KernelHost, 17 Tbps of global scrubbing and Arbor real-time filtering with 3.2 Tbps in Frankfurt am Main filter permanently and at no surcharge, with no null routing and no switchover window.
If your project already runs at KernelHost, the filtering is active without you doing anything. If you still notice something unusual, open a support ticket so our team can fine-tune the filter rules for your IP address. 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 frame in your time zone, and what you are seeing (players being thrown out, server missing from the browser, high ping). That saves a round of follow-up questions, and those count while a match is running.
If you host elsewhere and get shot at regularly, moving to KernelHost is a shorter path than another rule on a server whose uplink ends first. The always-on protection is part of every server package, not an add-on you book once the trouble starts.
Frequently asked questions
Which ports does a Counter-Strike 1.6 server really need?
Does RCON on Counter-Strike 1.6 use TCP or UDP?
Why does sv_max_queries_sec have no effect on my 1.6 server?
Why does my GoldSrc server become a tool against other targets?
My players see No challenge for your address. What does that mean?
Does a maintained server binary protect better than the unmodified one from Valve?
Does this article also cover Half-Life 2: Deathmatch and Sven Co-op?
My server vanished from the server browser but keeps running. Why?
Can I simply rate limit port 27015 while the server is being attacked?
At what attack size does no firewall rule help any more?
Does my server at KernelHost go offline during an attack?
When do I additionally need Advanced DDoS Protection?
2026 KernelHost GmbH. All rights reserved. This guide is protected by copyright. Republishing it on other websites, in whole, in part or in edited form, is not permitted without our written consent. Quoting with a source credit and a link is expressly welcome.

