Game servers are frequent DDoS targets because most games use UDP, a single IP address serves all players, and even a short spike in packet loss is visible to everyone. Protection works in layers: network-level mitigation upstream absorbs large floods, and filtering on the server itself handles smaller floods, abusive single sources and exposed services. In this tutorial you will harden an Ubuntu 24.04 game server with a minimal firewall, kernel SYN flood protection and per-source rate limits in nftables, and learn how to tell what kind of attack you are facing.

Prerequisites

To follow this guide you need:

  • A server running Ubuntu 24.04 LTS, for example a CubePath VPS, with a game server already running.
  • A non-root user with sudo privileges.
  • The game's ports and protocols. This guide uses a Source engine server on UDP and TCP port 27015 as the example; replace it with your game's ports.
  • Console access from your provider's panel in case a firewall mistake locks you out of SSH.

What a single server can and cannot do

It is important to be realistic about what filtering on the server can achieve:

AttackExampleCan the server filter it?
VolumetricUDP floods, DNS or NTP amplification at many GbpsNo. The traffic saturates the link before it reaches the firewall. Only upstream mitigation helps.
ProtocolSYN floods, floods of spoofed small packetsPartly. SYN cookies and early drops keep the kernel responsive at moderate rates.
Single-source abuseOne client or a few IPs sending far more packets than a player wouldYes. Per-source rate limits handle this well.
ApplicationQuery spam, fake connection attempts to the gamePartly. Rate limits help; the rest depends on the game server itself.

If an attack fills your network port, every rule in this guide runs too late. The measures below keep smaller attacks from taking the server down and reduce the attack surface, which is still worth doing.

Step 1 - Reducing the attack surface with UFW

Every open port is something an attacker can target. Allow only SSH and the game's ports, and deny everything else inbound:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw limit OpenSSH
sudo ufw allow 27015/udp
sudo ufw allow 27015/tcp
sudo ufw enable

ufw limit blocks an IP that opens 6 or more SSH connections within 30 seconds, which stops basic brute force attempts. Check the result:

sudo ufw status verbose
Status: active
Default: deny (incoming), allow (outgoing), disabled (routed)

To                         Action      From
--                         ------      ----
22/tcp (OpenSSH)           LIMIT IN    Anywhere
27015/udp                  ALLOW IN    Anywhere
27015/tcp                  ALLOW IN    Anywhere

Do not expose admin interfaces such as RCON, web panels or databases to the whole Internet. If you need them, allow them only from your own IP, for example sudo ufw allow from your_admin_ip to any port 27020 proto tcp, or reach them over SSH or a VPN.

Step 2 - Enabling kernel SYN flood protection

A SYN flood fills the table of half-open TCP connections so real clients cannot connect. SYN cookies let the kernel answer without storing state when that table fills. Ubuntu enables them by default; make the setting explicit and raise the backlog in a sysctl file:

sudo nano /etc/sysctl.d/60-ddos-hardening.conf
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 4096
net.ipv4.tcp_synack_retries = 2
net.ipv4.icmp_ignore_bogus_error_responses = 1

tcp_synack_retries = 2 makes the kernel give up on unanswered handshakes sooner, freeing slots faster. Apply and verify:

sudo sysctl --system
sysctl net.ipv4.tcp_syncookies net.ipv4.tcp_max_syn_backlog
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 4096

Step 3 - Rate limiting each source with nftables

A player sends a fairly steady number of packets per second, usually well under 200. A flooding host sends far more. nftables can track the packet rate of every source address and drop traffic from any address that exceeds a limit, without affecting other players.

You will put these rules in their own nftables table. It runs before UFW's rules and only drops offending traffic; everything else continues to UFW as usual. Create the rules file:

sudo nano /etc/nftables-gameshield.nft
table inet gameshield
delete table inet gameshield

table inet gameshield {
    set flood4 {
        type ipv4_addr
        flags dynamic, timeout
        timeout 1m
    }

    set flood6 {
        type ipv6_addr
        flags dynamic, timeout
        timeout 1m
    }

    set syn4 {
        type ipv4_addr
        flags dynamic, timeout
        timeout 1m
    }

    set syn6 {
        type ipv6_addr
        flags dynamic, timeout
        timeout 1m
    }

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

        ct state invalid counter drop

        udp dport 27015 update @flood4 { ip saddr limit rate over 300/second burst 500 packets } counter drop
        udp dport 27015 update @flood6 { ip6 saddr limit rate over 300/second burst 500 packets } counter drop

        tcp dport 27015 tcp flags syn update @syn4 { ip saddr limit rate over 20/second burst 40 packets } counter drop
        tcp dport 27015 tcp flags syn update @syn6 { ip6 saddr limit rate over 20/second burst 40 packets } counter drop
    }
}

How the file works:

  • The first two lines create the table and delete it again, so loading the file twice replaces the rules instead of failing.
  • The flood4 and flood6 sets keep a UDP packet rate per source address, and syn4 and syn6 do the same for new TCP connections. An entry disappears one minute after its source goes quiet.
  • The UDP rules drop packets from any single address that sends more than 300 packets per second to the game port, after an initial burst of 500.
  • The TCP rules limit how fast a single address can open new connections to the game port.
  • ct state invalid drops packets that do not belong to any valid connection, such as stray ACKs from spoofed floods.

The 300 packets per second limit is a starting point. Some fast-paced games send more; check real player traffic in Step 5 before tightening it.

Load the file and list the result:

sudo nft -f /etc/nftables-gameshield.nft
sudo nft list table inet gameshield

The output repeats your rules with counters set to zero.

Loading the rules at boot

Create a small systemd unit that loads the file at startup and removes the table when stopped:

sudo nano /etc/systemd/system/nftables-gameshield.service
[Unit]
Description=Per-source flood limits for the game server
Wants=network-pre.target
Before=network-pre.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/nft -f /etc/nftables-gameshield.nft
ExecStop=/usr/sbin/nft delete table inet gameshield

[Install]
WantedBy=multi-user.target

Enable it:

sudo systemctl daemon-reload
sudo systemctl enable --now nftables-gameshield.service
systemctl is-active nftables-gameshield.service
active

Step 4 - Checking connection tracking capacity

The firewall tracks every connection, including every UDP source that talks to the game. A flood from many spoofed addresses can fill the tracking table, after which the kernel drops new connections and logs nf_conntrack: table full, dropping packet. Compare the current count with the maximum:

cat /proc/sys/net/netfilter/nf_conntrack_count /proc/sys/net/netfilter/nf_conntrack_max
412
262144

If you see the table full message in sudo dmesg during attacks and the server has RAM to spare, double the limit at runtime and check that the problem goes away:

sudo sysctl -w net.netfilter.nf_conntrack_max=524288

Each tracked connection uses a few hundred bytes of kernel memory, so this is cheap on servers with several GB of RAM.

Step 5 - Watching traffic during an attack

When players report lag, first find out what is arriving. Show the total packet and byte rates per interface:

sudo apt install iftop tcpdump
sudo iftop -n -i eth0

Replace eth0 with your interface name from ip -br link. A sudden jump in received traffic, far above your normal player traffic, points to a flood.

Capture a sample of packets to the game port to see where they come from and how large they are:

sudo tcpdump -ni eth0 -c 50 udp port 27015

Many different source addresses with identical small packets suggest a spoofed flood. One or a few addresses with a very high rate is exactly what Step 3 handles. Check how many packets the rate limits have dropped:

sudo nft list chain inet gameshield input

The counter packets ... bytes ... value on each rule is the number of packets it dropped. The sets themselves (sudo nft list set inet gameshield flood4) list every address seen in the last minute, players included, so use the counters and tcpdump rather than the set contents to identify abusive sources.

If received traffic is close to your port speed and the counters barely move, the attack is volumetric. Filtering on the server cannot help with it; contact your provider with the time of the attack, the target IP and port, and a short tcpdump sample so they can apply or tune network-level mitigation.

Step 6 - Hardening the game server itself

Many attacks target the game application rather than the network. A few general measures help regardless of the game:

  • Keep the server updated. Most game server updates also fix crash bugs that attackers use.
  • Disable RCON, remote consoles and query ports you do not use, or bind them to 127.0.0.1.
  • Set sensible player and connection limits in the game configuration, and enable any built-in rate limiting or anti-spam options the game offers.
  • Do not publish the server's IP address alongside admin panels, websites or voice servers on the same machine. Hosting those elsewhere keeps an attack on one from taking down the others.

Troubleshooting

Legitimate players are disconnected with the rules active: the per-source limit is too low for your game. Look at a normal session with sudo tcpdump -ni eth0 udp port 27015 and host player_ip for ten seconds, estimate the packet rate, raise the limit with enough margin, and reload with sudo systemctl restart nftables-gameshield.

Players behind the same public IP are affected: players on the same network or a carrier-grade NAT share an address, so their packet rates add up. Raise the limit or accept that rare case.

You locked yourself out of SSH: use your provider's web console, then run sudo ufw allow OpenSSH or sudo systemctl stop nftables-gameshield to recover.

Conclusion

Your game server now accepts traffic only on the ports it needs, handles SYN floods with SYN cookies, drops packets from any single source that sends far more than a player would, and you know how to identify an attack while it happens. These measures stop small and single-source attacks; large volumetric floods need mitigation in front of the server. As next steps, tune the per-source limits from real player traffic, set up monitoring that alerts on sudden spikes in inbound traffic, and document a short checklist for contacting your provider during an attack.