Slowloris: how nftables and conntrack kill slow attacks
If you think nftables conntrack and modern stateful firewalls have made Slowloris attacks obsolete, think again. In 2026, poorly configured servers—especially game hosting platforms and low-latency services—remain vulnerable to this decade-old layer-7 attack. This article explains how Slowloris still bypasses default configurations, why connection tracking alone isn't enough, and how to deploy a robust nftables + conntrack defense that actually stops slow HTTP floods at the kernel level.
Slowloris doesn't flood bandwidth or exhaust CPU with brute-force packet storms. Instead, it quietly holds open hundreds or thousands of incomplete HTTP connections, starving the web server's connection pool until legitimate users are locked out. Because each malicious connection sends valid TCP handshakes and trickles minimal data, shallow rate-limiters and stateless firewalls see nothing unusual. The attack succeeds not by breaking the protocol, but by exploiting the gap between TCP state and application-layer readiness.
Why Slowloris Still Works in 2026
Modern kernels ship with connection tracking enabled by default, and administrators assume conntrack's stateful inspection will reject incomplete or malicious sessions. The reality is more nuanced.
The Mechanics of a Slowloris Attack
Slowloris opens legitimate TCP connections to port 80 or 443, completes the three-way handshake, then sends partial HTTP headers one byte at a time—or simply keeps the connection alive with periodic keep-alive packets. From the kernel's perspective:
- The TCP state machine sees
ESTABLISHEDconnections. - Conntrack marks them as
ct state established. - Application servers (Apache, Nginx, game panel APIs) reserve a worker thread or connection slot.
- Because the HTTP request never completes, the slot stays occupied until timeout.
A single attacker with 1,000 sockets can exhaust a typical Apache MaxRequestWorkers pool (default 150–400) in seconds. The server never crashes—it just stops accepting new connections.
Why Default Conntrack Rules Miss the Attack
Basic nftables conntrack rules often look like this:
nft add rule inet filter input ct state established,related accept
nft add rule inet filter input ct state new tcp dport { 80, 443 } accept
This policy correctly tracks connection state, but it has no awareness of application-layer progress. Once the handshake completes, conntrack sees an established flow. It doesn't know the HTTP request is incomplete, the payload is trickling at 1 byte per 10 seconds, or that the client will never send the final \r\n\r\n delimiter.
Game server control panels (Pterodactyl, Pelican) and public-facing APIs suffer the same fate: they accept the TCP connection, allocate resources, then wait indefinitely for a request that never arrives. Logs show no malformed packets, no SYN floods, no traditional DDoS signature—just silent resource exhaustion.
Hardening nftables Conntrack Against Slowloris
Effective mitigation requires layering stateful connection tracking with per-connection rate-limits, aggressive timeout tuning, and new-connection throttling. Here's how to build a defense that works.
Step 1: Reduce Conntrack Timeout for Established TCP
The default tcp_established timeout is often 5 days (432,000 seconds). Slowloris thrives in this window. Shrink it:
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=600
echo "net.netfilter.nf_conntrack_tcp_timeout_established = 600" >> /etc/sysctl.conf
Now idle connections die after 10 minutes instead of days. Legitimate HTTP clients complete requests in milliseconds; Slowloris attackers holding thousands of sockets will see their connections forcibly closed.
Step 2: Rate-Limit New Connections with nftables Meters
Prevent a single source IP from opening hundreds of simultaneous connections:
nft add table inet pakkt
nft add chain inet pakkt input { type filter hook input priority 0 \; policy drop \; }
nft add rule inet pakkt input ct state established,related accept
nft add rule inet pakkt input ct state new tcp dport { 80, 443 } \
meter http_new { ip saddr limit rate over 20/second burst 40 packets } drop
nft add rule inet pakkt input ct state new tcp dport { 80, 443 } accept
This meter allows each source IP to open 20 new HTTP(S) connections per second, with a burst allowance of 40. A Slowloris script trying to spawn 1,000 connections will hit the limit immediately, and excess SYNs are dropped before they ever reach Apache or Nginx.
Step 3: Track Concurrent Connections per IP
Even if an attacker spreads connection attempts over time, you can enforce a hard ceiling on simultaneous established sessions:
nft add rule inet pakkt input tcp dport { 80, 443 } \
meter http_concurrent { ip saddr ct count over 50 } drop
Now any single IP holding more than 50 concurrent connections to your web services will have new attempts rejected. Adjust the threshold based on your legitimate traffic profile—CDN health-checks and API clients rarely need more than a handful of parallel sockets.
Step 4: Combine with XDP for Sub-Microsecond Early Drop
While nftables operates in the kernel's network stack (after the packet has been allocated an skb), XDP intercepts packets at the NIC driver level—before memory allocation. For very high connection-rate attacks, pushing the per-IP SYN rate-limit into XDP saves CPU cycles:
// Pseudo-code: PAKKT Engine rule
rule_type: rate_limit
port_range: 80-443
protocol: TCP
max_port_pps: 10000 // Global ceiling across all IPs
When the attacker's botnet sends 50,000 SYNs/sec, XDP drops 40,000 before they ever reach conntrack. The remaining 10,000 are then subject to the nftables meter and concurrent-connection limits. This layered approach—XDP for stateless packet rate-limiting, nftables conntrack for stateful per-IP session tracking—delivers both performance and precision.
PAKKT.io automates this dual-layer defense: configure your XDP rule in the web panel, and the agent compiles it into the BPF map in real time; separately, define your nftables stateful rules in the isolated inet pakkt table, with zero conflict against Docker's nat chains or fail2ban's filter table.
Real-World Configuration Example: Protecting a Pterodactyl Panel
Game server control panels expose both a web UI (port 443) and an internal API used by game nodes. A Slowloris attack can lock out administrators and prevent nodes from reporting status. Here's a complete nftables ruleset:
#!/usr/sbin/nft -f
flush table inet pakkt
table inet pakkt {
chain input {
type filter hook input priority 0; policy drop;
# Loopback
iif lo accept
# Established/related
ct state established,related accept
ct state invalid drop
# ICMP essential types
icmp type { echo-request, destination-unreachable, time-exceeded } limit rate 10/second accept
# SSH (protected by fail2ban in a separate table, so we allow here)
tcp dport 22 ct state new accept
# HTTP/HTTPS: new-connection rate-limit + concurrent ceiling
tcp dport { 80, 443 } meter http_new { ip saddr limit rate over 20/second burst 40 packets } drop
tcp dport { 80, 443 } meter http_concurrent { ip saddr ct count over 50 } drop
tcp dport { 80, 443 } ct state new accept
# Panel API on 8080 (internal nodes only, IP whitelist)
tcp dport 8080 ip saddr @panel_nodes accept
# Drop everything else
log prefix "pakkt-drop: " drop
}
}
# Define the whitelist set for panel nodes
nft add set inet pakkt panel_nodes { type ipv4_addr \; }
nft add element inet pakkt panel_nodes { 10.0.1.5, 10.0.1.6, 10.0.1.7 }
This configuration ensures:
- Each public IP can open at most 20 HTTPS connections/sec, burst to 40.
- No single IP can hold more than 50 concurrent sessions.
- Internal API port (8080) is locked to whitelisted node IPs.
- Established connections are subject to the 600-second idle timeout (set via sysctl).
Deploy this alongside the Pterodactyl integration available at PAKKT Integrations, and you gain centralized visibility: the PAKKT dashboard will show per-port packet rates, top source IPs, and GeoIP mapping of connection attempts—making it trivial to spot a Slowloris campaign and add offending subnets to the XDP blocklist with one click.
Monitoring and Tuning Your Defense
Static rules are only half the battle. Effective Slowloris mitigation requires continuous observation and adjustment.
Track Conntrack Table Usage
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
If nf_conntrack_count approaches nf_conntrack_max (default often 65536), you're at risk of conntrack table exhaustion—a secondary DoS vector. Raise the maximum if your server legitimately handles high connection concurrency, or tighten your new-connection rate-limits to prevent table overflow.
Inspect Active Meters
nft list meter inet pakkt http_new
nft list meter inet pakkt http_concurrent
You'll see which IPs are currently subject to rate-limiting or concurrent-connection caps. Cross-reference these with your application logs: if legitimate users appear in the meter, your thresholds may be too aggressive.
Audit Logs for Dropped Packets
The log prefix "pakkt-drop: " directive writes rejected packets to /var/log/kern.log or your syslog destination. Parse these logs to identify attack patterns:
grep "pakkt-drop" /var/log/kern.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -20
This one-liner shows the top 20 source IPs that triggered drops. If you see a single IP with thousands of entries in a short window, add it to your XDP or nftables blocklist.
For centralized, real-time monitoring across multiple servers, the PAKKT agent streams per-port metrics and audit events to the panel's TimescaleDB backend. You can graph connection rates, overlay GeoIP data, and set threshold alerts—all without SSH-ing into each box. Learn more at the PAKKT Blog, where we publish weekly tutorials on kernel-level defense patterns.
Beyond HTTP: Slowloris Variants Targeting Game Protocols
While the original Slowloris targeted HTTP, the same slow-connection principle applies to any line-based or message-framed protocol. Game server query ports (Source engine 27015, Minecraft RCON 25575) can be held open with incomplete payloads. The mitigation strategy is identical:
- Reduce the established timeout for the relevant protocol (if using a custom conntrack helper, or fall back to the generic TCP timeout).
- Rate-limit new connections per source IP.
- Enforce a ceiling on concurrent sessions.
- Use XDP to drop excessive SYN floods before they reach conntrack.
For example, protecting a Minecraft server on port 25565:
nft add rule inet pakkt input tcp dport 25565 \
meter minecraft_new { ip saddr limit rate over 10/second burst 20 packets } drop
nft add rule inet pakkt input tcp dport 25565 \
meter minecraft_concurrent { ip saddr ct count over 30 } drop
nft add rule inet pakkt input tcp dport 25565 ct state new accept
Combine this with an XDP rule that enforces a global 5,000 pps ceiling on port 25565, and you have a defense that scales from single-server setups to multi-node clusters.
Complementary Measures: Application-Layer Hardening
While nftables conntrack and XDP provide kernel-level protection, application configuration must also be tightened.
Web Server Tuning
- Apache: Lower
Timeout(default 60s → 10s), reduceKeepAliveTimeout(default 5s → 2s), setRequestReadTimeout header=10 body=30. - Nginx:
client_header_timeout 10s;,client_body_timeout 30s;,keepalive_timeout 5s;. - Limit request size:
LimitRequestBody(Apache) orclient_max_body_size(Nginx) to prevent memory exhaustion from slow POST bodies.
Reverse Proxy with Connection Queueing
If your architecture permits, place Nginx or HAProxy in front of your application server. The proxy terminates the slow client connection and forwards only complete, validated requests upstream—shielding the application from incomplete HTTP floods. However, this shifts the Slowloris problem to the proxy; you must still apply the nftables and XDP rate-limits at the proxy host.
ModSecurity or Application Firewall Rules
Layer-7 WAFs can detect incomplete HTTP requests and close connections proactively. PAKKT does not provide L7 inspection (we operate at L2/L3 in XDP and L3/L4 in nftables), but you can run ModSecurity on the same server. The two layers are complementary: PAKKT drops malicious packets before they consume socket buffers; ModSecurity inspects the HTTP payload for application-layer anomalies.
For a detailed comparison of kernel-layer vs. application-layer defenses, see the technical reference at netfilter.org documentation, which explains conntrack internals and hook priority.
Conclusion
Slowloris remains effective in 2026 because default conntrack configurations track TCP state without enforcing application-layer readiness. By shrinking established timeouts, deploying nftables meters for new-connection and concurrent-session rate-limits, and layering XDP for high-performance packet filtering, you close the gap that Slowloris exploits. The result is a resilient, kernel-level defense that protects web panels, APIs, and game services without sacrificing legitimate traffic or adding perceptible latency.
FAQ
Can I rely solely on application-layer timeouts to stop Slowloris, or do I need nftables conntrack rules?
Application timeouts (Apache RequestReadTimeout, Nginx client_header_timeout) are essential but insufficient on their own. A determined attacker can send just enough data to reset the timeout counter, keeping connections alive indefinitely. Kernel-level nftables conntrack rate-limits and concurrent-connection ceilings enforce hard boundaries before packets ever reach the application, preventing socket exhaustion at the source.
How do I choose the right threshold for the nftables meter new-connection rate-limit?
Start by profiling legitimate traffic: use nft counters or application access logs to measure how many connections per second a typical user or API client opens. Add a 2–3× safety margin to account for bursts (page loads with many assets, parallel API calls). For public-facing game panels, 10–20 new connections/sec per IP is usually safe; for internal APIs, 5/sec may suffice. Monitor the meter with nft list meter and adjust if you see false positives.
Does reducing nf_conntrack_tcp_timeout_established break long-lived WebSocket or SSH sessions?
No, as long as those sessions exchange periodic traffic. Conntrack resets the idle timer every time a packet flows in either direction. SSH keep-alives (default every 60s) and WebSocket pings easily stay within a 600-second timeout. Only truly silent connections—like a Slowloris socket sending 1 byte every 15 minutes—will be dropped. If you run services that require multi-hour idle tolerance, create a separate conntrack timeout policy for specific ports using nft ct timeout custom objects.
Deploy PAKKT in 30 seconds
Dual-layer kernel protection. XDP + nftables. Driven from a central panel. 7-day free trial.