When Local Firewall Rules Help (and When They Don't)
Local firewall rules are your first line of defense when a DDoS attack is moderate enough that your server's NIC and uplink can still handle the raw packet volume. If the attack is 500 Mbps and your server has a 1 Gbps link, iptables or nftables can drop the malicious traffic before it reaches your application. Your kernel processes the packets and discards them in the network stack, keeping your services responsive.
However, local rules become useless when the attack saturates your uplink. If 10 Gbps of traffic is hitting your 1 Gbps port, the packets are already being dropped by your upstream router or switch before they even reach your kernel. No amount of firewall rules on the host can help because the bottleneck is the physical link, not the software stack. In that scenario you need upstream mitigation: scrubbing centers, BGP blackholing, or a DDoS protection service that filters traffic before it reaches your network.
That said, local rules are still worth maintaining even if you have upstream protection. Upstream mitigation takes time to activate. BGP propagation can take 30 seconds to several minutes. During that window, your local firewall rules keep your server alive. They are also effective against low-and-slow attacks, application-layer floods, and scanning that upstream filters may not catch.
iptables vs nftables
nftables is the modern replacement for iptables in the Linux kernel. It was merged into the kernel in version 3.13 (2014) and has been the default firewall framework in Debian 10+, Ubuntu 20.04+, RHEL 8+, and most current distributions. If you are setting up a new server today, you should use nftables.
That said, iptables is not gone. The iptables-nft compatibility layer translates iptables commands into nftables bytecode under the hood. On most modern systems, the iptables command is actually a symlink to iptables-nft. Your existing iptables rules still work; they are just being translated.
The key differences that matter for DDoS mitigation:
- Performance: nftables uses a single-pass classifier and supports sets/maps natively, making it significantly faster when you have hundreds or thousands of rules or IP addresses to match against.
- Syntax: nftables uses a more structured syntax with named tables, chains, and rule handles. It is more verbose for simple rules but more powerful for complex rulesets.
- Atomic updates: nftables can replace an entire ruleset atomically with
nft -f, avoiding the brief window of no-rules that can happen with iptables-restore. - Sets: nftables has built-in support for IP sets, replacing the need for the separate
ipsetutility that iptables requires for efficient IP blacklisting.
In this guide, every rule is shown in both syntaxes so you can use whichever matches your environment.
Foundation Rules: Apply to Every Server
Before addressing specific attack types, every server should have a baseline set of rules that drop obviously malicious traffic. These rules cost almost nothing in performance and catch a surprising amount of junk.
Drop Invalid Packets
Invalid packets are those that do not match any known connection and do not have valid flags for starting a new connection. They are generated by scanners, broken attack tools, and spoofed traffic.
# iptables iptables -A INPUT -m conntrack --ctstate INVALID -j DROP # nftables nft add rule inet filter input ct state invalid drop
Drop Bogon Source IPs
Bogon addresses are IP ranges that should never appear as source addresses on the public internet. Dropping them eliminates a large class of spoofed traffic.
# iptables
iptables -A INPUT -s 0.0.0.0/8 -j DROP
iptables -A INPUT -s 10.0.0.0/8 -j DROP
iptables -A INPUT -s 100.64.0.0/10 -j DROP
iptables -A INPUT -s 127.0.0.0/8 -j DROP
iptables -A INPUT -s 169.254.0.0/16 -j DROP
iptables -A INPUT -s 172.16.0.0/12 -j DROP
iptables -A INPUT -s 192.0.0.0/24 -j DROP
iptables -A INPUT -s 192.0.2.0/24 -j DROP
iptables -A INPUT -s 192.168.0.0/16 -j DROP
iptables -A INPUT -s 198.18.0.0/15 -j DROP
iptables -A INPUT -s 198.51.100.0/24 -j DROP
iptables -A INPUT -s 203.0.113.0/24 -j DROP
iptables -A INPUT -s 224.0.0.0/4 -j DROP
iptables -A INPUT -s 240.0.0.0/4 -j DROP
# nftables (using a set for efficiency)
nft add set inet filter bogons { type ipv4_addr \; flags interval \; }
nft add element inet filter bogons { \
0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8, \
169.254.0.0/16, 172.16.0.0/12, 192.0.0.0/24, 192.0.2.0/24, \
192.168.0.0/16, 198.18.0.0/15, 198.51.100.0/24, \
203.0.113.0/24, 224.0.0.0/4, 240.0.0.0/4 \
}
nft add rule inet filter input ip saddr @bogons drop
If your server is behind a private network or uses RFC 1918 addresses internally, remove the corresponding ranges from the bogon list. Dropping 10.0.0.0/8 on a server that receives traffic from a 10.x load balancer will break everything.
Limit ICMP
ICMP is necessary for path MTU discovery and diagnostics, but unlimited ICMP can be abused for ping floods. Rate limiting keeps ICMP functional while preventing abuse.
# iptables iptables -A INPUT -p icmp --icmp-type echo-request \ -m limit --limit 1/s --limit-burst 4 -j ACCEPT iptables -A INPUT -p icmp --icmp-type echo-request -j DROP # nftables nft add rule inet filter input icmp type echo-request \ limit rate 1/second burst 4 packets accept nft add rule inet filter input icmp type echo-request drop
Drop Fragmented Packets
Fragmented packets are rarely legitimate on modern networks (path MTU discovery prevents fragmentation in normal operation). They are frequently used in evasion techniques and fragment-based attacks.
# iptables iptables -A INPUT -f -j DROP # nftables nft add rule inet filter input ip frag-off != 0 drop
SYN Flood Protection
SYN floods remain the most common volumetric attack type. They exploit the TCP handshake by sending millions of SYN packets with spoofed source IPs. Each SYN causes the server to allocate memory for a half-open connection. The server's connection table fills up and legitimate users cannot connect.
Step 1: Enable SYN Cookies
SYN cookies are a kernel mechanism that avoids allocating connection state for SYN packets. The server encodes the connection parameters into the SYN-ACK sequence number. Only when the client completes the handshake with a valid ACK does the kernel create a full connection entry. This eliminates the connection-table exhaustion attack entirely.
# Enable SYN cookies and tune the backlog sysctl -w net.ipv4.tcp_syncookies=1 sysctl -w net.ipv4.tcp_max_syn_backlog=65536 sysctl -w net.ipv4.tcp_synack_retries=2
Make these persistent by adding them to /etc/sysctl.d/99-ddos.conf.
Step 2: Rate Limit New TCP Connections with hashlimit
The hashlimit module provides per-source-IP rate limiting, which is far more effective than a global rate limit. A global limit of 500 SYN/s means a single attacker using one IP can consume the entire budget. Per-source limiting ensures each IP gets its own bucket.
# iptables: per-source SYN rate limiting
iptables -A INPUT -p tcp --syn -m hashlimit \
--hashlimit-name syn_flood \
--hashlimit-mode srcip \
--hashlimit-above 30/s \
--hashlimit-burst 50 \
--hashlimit-htable-expire 30000 \
-j DROP
# nftables: per-source SYN rate limiting
nft add rule inet filter input tcp flags syn / syn,ack \
meter syn_flood { ip saddr limit rate over 30/second burst 50 packets } drop
This allows each source IP up to 30 SYN packets per second with a burst of 50. Legitimate users rarely exceed 5-10 SYN packets per second to any single server, so 30/s provides generous headroom while stopping flood sources.
Step 3: Limit Concurrent Half-Open Connections
# iptables: max 20 half-open connections per source
iptables -A INPUT -p tcp --syn -m connlimit \
--connlimit-above 20 --connlimit-mask 32 -j DROP
# nftables equivalent using a meter
nft add rule inet filter input tcp flags syn / syn,ack \
meter conn_limit { ip saddr ct count over 20 } drop
UDP Flood Protection
UDP floods are the second most common DDoS vector. Because UDP is connectionless, there is no handshake to exploit. Attackers simply blast high volumes of UDP packets at the target. Amplification attacks (DNS, NTP, memcached, CLDAP) use UDP to achieve amplification factors of 50x or more.
Rate Limit UDP per Source
# iptables: limit UDP to 100 packets/s per source
iptables -A INPUT -p udp -m hashlimit \
--hashlimit-name udp_flood \
--hashlimit-mode srcip \
--hashlimit-above 100/s \
--hashlimit-burst 150 \
--hashlimit-htable-expire 30000 \
-j DROP
# nftables
nft add rule inet filter input ip protocol udp \
meter udp_flood { ip saddr limit rate over 100/second burst 150 packets } drop
Drop UDP on Unused Ports
If your server only needs UDP on specific ports (for example, DNS on 53 or a game server on a custom port), drop all other inbound UDP. This eliminates entire classes of amplification traffic.
# iptables: allow only specific UDP ports, drop the rest iptables -A INPUT -p udp --dport 53 -j ACCEPT iptables -A INPUT -p udp -j DROP # nftables nft add rule inet filter input udp dport 53 accept nft add rule inet filter input ip protocol udp drop
Block DNS Amplification
DNS amplification attacks use spoofed queries to open DNS resolvers. The responses (which can be 50x larger than the query) are directed at the victim. If your server is not expecting DNS responses from external resolvers, drop inbound UDP from source port 53.
# iptables: drop unsolicited DNS responses iptables -A INPUT -p udp --sport 53 -m conntrack \ --ctstate NEW -j DROP # nftables nft add rule inet filter input udp sport 53 ct state new drop
Block NTP Amplification
NTP amplification exploits the monlist command on misconfigured NTP servers. The amplification factor can reach 556x. Drop inbound UDP from source port 123 if your server does not use NTP over UDP from external sources (most modern systems use chrony which handles this differently).
# iptables: drop unsolicited NTP responses iptables -A INPUT -p udp --sport 123 -m conntrack \ --ctstate NEW -j DROP # nftables nft add rule inet filter input udp sport 123 ct state new drop
These rules use conntrack to only drop NEW packets from these source ports. Responses to outbound queries your server initiated (ESTABLISHED state) are still allowed through. Your server can still query DNS and sync time.
Connection Limiting
Even without a volumetric flood, an attacker can exhaust your server's resources by opening many connections and holding them open. Slowloris, R-U-Dead-Yet, and similar application-layer attacks use this technique. Connection limiting caps the number of concurrent connections from any single source.
connlimit Module
# iptables: max 100 concurrent connections per source IP
iptables -A INPUT -p tcp --dport 80 -m connlimit \
--connlimit-above 100 --connlimit-mask 32 -j DROP
iptables -A INPUT -p tcp --dport 443 -m connlimit \
--connlimit-above 100 --connlimit-mask 32 -j DROP
# nftables
nft add rule inet filter input tcp dport 80 \
meter http_conn { ip saddr ct count over 100 } drop
nft add rule inet filter input tcp dport 443 \
meter https_conn { ip saddr ct count over 100 } drop
A limit of 100 concurrent connections per IP is generous for web traffic. Most browsers open 6-8 connections per domain. If a single IP has 100 connections open simultaneously, it is either a proxy, a CDN node, or an attacker. Adjust upward if you serve traffic through known proxy IPs.
Conntrack Tuning
The connection tracking subsystem (conntrack) is the foundation of stateful firewalling. Under a DDoS attack, the conntrack table can fill up, causing the kernel to drop all new connections, including legitimate ones. Tuning conntrack parameters is critical.
# Increase the maximum tracked connections (default is often 65536) sysctl -w net.netfilter.nf_conntrack_max=2097152 # Increase hash table buckets (should be ~nf_conntrack_max/4) sysctl -w net.netfilter.nf_conntrack_buckets=524288 # Reduce timeouts to free entries faster sysctl -w net.netfilter.nf_conntrack_tcp_timeout_syn_recv=30 sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=30 sysctl -w net.netfilter.nf_conntrack_tcp_timeout_fin_wait=30 sysctl -w net.netfilter.nf_conntrack_tcp_timeout_close_wait=15 sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=600 sysctl -w net.netfilter.nf_conntrack_udp_timeout=30 sysctl -w net.netfilter.nf_conntrack_udp_timeout_stream=60
Memory impact: Each conntrack entry uses approximately 300 bytes of kernel memory. Setting nf_conntrack_max to 2,097,152 reserves about 600 MB. Ensure your server has enough RAM to support this alongside your applications.
Rate Limiting with hashlimit
The hashlimit module (iptables) and meters (nftables) provide the most flexible rate limiting for DDoS mitigation. Unlike the basic limit module which applies a global rate, hashlimit creates separate rate buckets per source IP, destination IP, source port, or destination port.
Per-Source-IP Rate Limiting
# iptables: limit all inbound traffic to 1000 packets/s per source
iptables -A INPUT -m hashlimit \
--hashlimit-name global_rate \
--hashlimit-mode srcip \
--hashlimit-above 1000/s \
--hashlimit-burst 1500 \
--hashlimit-htable-expire 60000 \
--hashlimit-htable-max 65536 \
-j DROP
# nftables
nft add rule inet filter input \
meter global_rate { ip saddr limit rate over 1000/second burst 1500 packets } drop
Per-Destination-Port Rate Limiting
This approach limits traffic per port, preventing any single service from being overwhelmed while leaving others unaffected.
# iptables: limit per destination port
iptables -A INPUT -p tcp -m hashlimit \
--hashlimit-name port_rate \
--hashlimit-mode dstport \
--hashlimit-above 5000/s \
--hashlimit-burst 8000 \
--hashlimit-htable-expire 60000 \
-j DROP
# nftables
nft add rule inet filter input tcp dport != 0 \
meter port_rate { tcp dport limit rate over 5000/second burst 8000 packets } drop
Burst Handling
The burst parameter is critical for avoiding false positives. Legitimate traffic is bursty: a user loads a page and their browser fires 20-30 requests in rapid succession, then goes quiet. Without a sufficient burst allowance, your rate limits will drop legitimate traffic during these natural bursts. Set the burst to 1.5x your per-second limit as a starting point, then adjust based on observed traffic patterns.
nftables Equivalents: Side-by-Side
For quick reference, here is a summary of the most important rule translations between iptables and nftables:
# Accept established connections
iptables: -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
nftables: ct state established,related accept
# Drop invalid packets
iptables: -A INPUT -m conntrack --ctstate INVALID -j DROP
nftables: ct state invalid drop
# SYN rate limit per source
iptables: -m hashlimit --hashlimit-name X --hashlimit-mode srcip --hashlimit-above 30/s
nftables: meter X { ip saddr limit rate over 30/second }
# Connection limit per source
iptables: -m connlimit --connlimit-above 100 --connlimit-mask 32
nftables: meter X { ip saddr ct count over 100 }
# ICMP rate limit
iptables: -p icmp -m limit --limit 1/s --limit-burst 4
nftables: icmp type echo-request limit rate 1/second burst 4 packets
# IP set matching
iptables: -m set --match-set blacklist src (requires ipset)
nftables: ip saddr @blacklist (native sets, no external tool)
Performance Considerations
Firewall performance matters during a DDoS attack because every microsecond spent processing a malicious packet is a microsecond not spent serving legitimate traffic. Here are the key factors:
- Rule count matters with iptables. iptables evaluates rules linearly from top to bottom. If you have 500 rules and the matching rule is number 499, every packet traverses 498 non-matching rules first. Under a 1 Mpps flood, that adds up fast. Place your most-hit rules (ESTABLISHED accept, common DROP rules) at the top of the chain.
- nftables sets and maps are O(1). Instead of 10,000 individual
-s x.x.x.x -j DROPrules (which iptables evaluates linearly), nftables can match against a set of 10,000 IPs in constant time using a hash table. This is the single biggest performance advantage of nftables for DDoS use cases. - Use ipset with iptables. If you must use iptables and need to match against large IP lists, use
ipsetto create hash-based sets. A single iptables rule with-m set --match-set blacklist srcreplaces thousands of individual rules. - Conntrack has a cost. Every tracked connection consumes CPU and memory. If you have services that do not need stateful tracking (for example, a stateless UDP service), you can use
-j NOTRACK(iptables) ornotrack(nftables) in the raw table to skip connection tracking for that traffic. - Consider XDP for extreme volumes. For attacks exceeding 1-2 Mpps, even nftables may not be fast enough. XDP (eXpress Data Path) processes packets before they reach the network stack, achieving 10-20 Mpps drop rates on commodity hardware. XDP is beyond the scope of this guide, but worth investigating for high-traffic environments.
# ipset example: create a blacklist and match against it
ipset create blacklist hash:ip maxelem 100000 timeout 3600
ipset add blacklist 203.0.113.50
ipset add blacklist 198.51.100.75
iptables -A INPUT -m set --match-set blacklist src -j DROP
# nftables equivalent (native sets)
nft add set inet filter blacklist { type ipv4_addr \; timeout 1h \; }
nft add element inet filter blacklist { 203.0.113.50, 198.51.100.75 }
nft add rule inet filter input ip saddr @blacklist drop
The DOCKER-USER Chain
If you run Docker, you need to know about the DOCKER-USER chain. Docker manipulates iptables extensively, creating its own chains and DNAT rules to route traffic to containers. The critical problem: rules you add to the INPUT chain do not apply to traffic destined for Docker containers. Container traffic goes through the FORWARD chain, bypassing INPUT entirely.
Docker provides the DOCKER-USER chain specifically for user-defined rules that should apply to container traffic. Rules in this chain are evaluated before Docker's own rules.
# WRONG: this does NOT protect Docker containers iptables -A INPUT -p tcp --syn -m hashlimit \ --hashlimit-name syn_limit --hashlimit-mode srcip \ --hashlimit-above 30/s --hashlimit-burst 50 -j DROP # CORRECT: this protects Docker containers iptables -I DOCKER-USER -p tcp --syn -m hashlimit \ --hashlimit-name syn_limit --hashlimit-mode srcip \ --hashlimit-above 30/s --hashlimit-burst 50 -j DROP # Block specific IPs from reaching containers iptables -I DOCKER-USER -s 203.0.113.0/24 -j DROP # Rate limit connections to a specific container port iptables -I DOCKER-USER -p tcp --dport 8080 -m connlimit \ --connlimit-above 50 --connlimit-mask 32 -j DROP
Always use -I DOCKER-USER (insert) instead of -A DOCKER-USER (append) to ensure your rules are evaluated before the default RETURN rule that Docker places at the end of the chain.
If you use Docker Compose or Docker Swarm, test your DOCKER-USER rules carefully. Docker recreates its iptables rules when containers start and stop. Your DOCKER-USER rules persist across container restarts, but you should verify they are still in place after a Docker daemon restart.
Automation: Flowtriq-Triggered Firewall Rules
Manually adding firewall rules during an attack is slow and error-prone. Flowtriq's auto-mitigation feature can trigger iptables or nftables rules automatically when an attack is detected, and remove them when the attack subsides.
The workflow:
- Flowtriq detects an attack within 1-2 seconds of onset, classifying it by type (SYN flood, UDP amplification, ICMP flood, etc.).
- Based on the attack characteristics, Flowtriq generates the appropriate firewall rules. For a SYN flood from a concentrated set of source IPs, it generates per-source DROP rules. For a widely distributed UDP flood, it generates rate-limiting rules on the affected ports.
- Flowtriq pushes the rules to your server via the node agent, which applies them immediately.
- When the attack ends, the rules are automatically removed to avoid blocking legitimate traffic.
This approach combines the precision of Flowtriq's detection (which identifies the exact attack vector and source characteristics) with the speed of local firewall rules (which take effect instantly, with no upstream propagation delay).
You can configure which mitigation actions Flowtriq is allowed to take in the auto-mitigation settings. Options range from notification-only (Flowtriq tells you what rules to apply) to fully automatic (Flowtriq applies and removes rules without human intervention).
Complete Hardened Ruleset Template
Below is a complete iptables-save format ruleset that you can adapt for your server. It incorporates all the rules discussed in this guide. Save it to a file and load it with iptables-restore < ruleset.rules.
*filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [0:0] # Allow loopback -A INPUT -i lo -j ACCEPT # Allow established and related connections (fast path) -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT # Drop invalid packets -A INPUT -m conntrack --ctstate INVALID -j DROP # Drop bogon source addresses -A INPUT -s 0.0.0.0/8 -j DROP -A INPUT -s 10.0.0.0/8 -j DROP -A INPUT -s 100.64.0.0/10 -j DROP -A INPUT -s 127.0.0.0/8 -j DROP -A INPUT -s 169.254.0.0/16 -j DROP -A INPUT -s 172.16.0.0/12 -j DROP -A INPUT -s 192.0.0.0/24 -j DROP -A INPUT -s 192.0.2.0/24 -j DROP -A INPUT -s 192.168.0.0/16 -j DROP -A INPUT -s 198.18.0.0/15 -j DROP -A INPUT -s 198.51.100.0/24 -j DROP -A INPUT -s 203.0.113.0/24 -j DROP -A INPUT -s 224.0.0.0/4 -j DROP -A INPUT -s 240.0.0.0/4 -j DROP # Drop fragmented packets -A INPUT -f -j DROP # Drop malformed TCP flags -A INPUT -p tcp --tcp-flags ALL NONE -j DROP -A INPUT -p tcp --tcp-flags ALL ALL -j DROP -A INPUT -p tcp --tcp-flags FIN,SYN FIN,SYN -j DROP -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j DROP -A INPUT -p tcp --tcp-flags FIN,RST FIN,RST -j DROP -A INPUT -p tcp --tcp-flags ACK,FIN FIN -j DROP -A INPUT -p tcp --tcp-flags ACK,URG URG -j DROP # ICMP rate limiting -A INPUT -p icmp --icmp-type echo-request -m limit \ --limit 1/s --limit-burst 4 -j ACCEPT -A INPUT -p icmp --icmp-type echo-request -j DROP -A INPUT -p icmp -j ACCEPT # SYN flood protection: per-source rate limiting -A INPUT -p tcp --syn -m hashlimit \ --hashlimit-name syn_flood \ --hashlimit-mode srcip \ --hashlimit-above 30/s \ --hashlimit-burst 50 \ --hashlimit-htable-expire 30000 -j DROP # SYN flood protection: per-source connection limit -A INPUT -p tcp --syn -m connlimit \ --connlimit-above 20 --connlimit-mask 32 -j DROP # UDP rate limiting per source -A INPUT -p udp -m hashlimit \ --hashlimit-name udp_flood \ --hashlimit-mode srcip \ --hashlimit-above 100/s \ --hashlimit-burst 150 \ --hashlimit-htable-expire 30000 -j DROP # Block amplification: drop unsolicited DNS/NTP responses -A INPUT -p udp --sport 53 -m conntrack --ctstate NEW -j DROP -A INPUT -p udp --sport 123 -m conntrack --ctstate NEW -j DROP -A INPUT -p udp --sport 11211 -m conntrack --ctstate NEW -j DROP -A INPUT -p udp --sport 1900 -m conntrack --ctstate NEW -j DROP -A INPUT -p udp --sport 389 -m conntrack --ctstate NEW -j DROP # Connection limiting for web services -A INPUT -p tcp --dport 80 -m connlimit \ --connlimit-above 100 --connlimit-mask 32 -j DROP -A INPUT -p tcp --dport 443 -m connlimit \ --connlimit-above 100 --connlimit-mask 32 -j DROP # Allow SSH (rate limited) -A INPUT -p tcp --dport 22 -m hashlimit \ --hashlimit-name ssh_limit \ --hashlimit-mode srcip \ --hashlimit-above 4/min \ --hashlimit-burst 6 \ --hashlimit-htable-expire 120000 -j ACCEPT -A INPUT -p tcp --dport 22 -j DROP # Allow HTTP and HTTPS -A INPUT -p tcp --dport 80 -j ACCEPT -A INPUT -p tcp --dport 443 -j ACCEPT COMMIT
Before applying, save your current rules as a backup:
iptables-save > /etc/iptables/rules.v4.backup iptables-restore < /path/to/hardened-ruleset.rules
Test thoroughly. Lock yourself out of SSH and you will have a bad day. Always have out-of-band console access (IPMI, cloud console, KVM) available before applying restrictive firewall rules remotely.
Flowtriq automates this. Instead of manually crafting and maintaining firewall rules, Flowtriq monitors your traffic in real time, detects attacks within seconds, and can apply targeted mitigation rules automatically. Rules are tailored to the specific attack vector, not generic templates. Start your 7-day free trial to see it in action.