Quick answer
An ACK flood is a protocol denial-of-service attack that sends a high rate of TCP packets with the ACK flag set — plain ACK, SYN-ACK or PSH-ACK — that match no established connection. Each looks ordinary alone, so a stateful firewall must look it up in its connection table before discarding it. It exhausts per-packet work and state tables, not bandwidth, so a flood that looks modest on a graph can still fell the device fronting a healthy server.
01What is an ACK flood?
An ACK flood is a stream of TCP packets carrying the acknowledgement (ACK) flag that do not match any connection the target is tracking. In a normal session the ACK flag confirms received data; it is set on almost every packet after the handshake. Attackers abuse that ubiquity: a lone ACK is indistinguishable from legitimate mid-session traffic until something checks whether a matching connection exists.
That check is the whole point. A firewall, load balancer or server that keeps per-connection state must consult its table for every arriving ACK, decide the packet is orphaned, and drop it. At high packet rates that lookup cost — not the raw data volume — is what runs out first.
The family has three common members that share one weakness and differ only in the flags set. They are often mixed inside the same campaign, and frequently layered on top of a SYN flood so the defender is fighting handshake exhaustion and orphaned-packet exhaustion at once.
02ACK, SYN-ACK and PSH-ACK: three variants
All three set ACK. What changes is the extra flag and, with it, the type of bookkeeping the attacker tries to trip.
- A SYN-ACK flood doubles as a reflection attack: the attacker spoofs the victim's address in SYN packets to many servers, and those servers' SYN-ACK replies all land on the victim
- A PSH-ACK flood, also called a push flood, carries a payload the PSH flag forces the receiver to process, so it costs slightly more per packet than a bare ACK
- Real sessions send ACKs constantly, so none of these can be blocked by flag alone without breaking every connection on the box
| Variant | Flags set | What it abuses |
|---|---|---|
| Plain ACK flood | ACK | Connection-table lookups for packets that match nothing |
| SYN-ACK flood | SYN + ACK | Looks like a reply to a SYN the victim never sent — often reflected off third parties |
| PSH-ACK flood | PSH + ACK | PSH tells the stack to deliver data up immediately, forcing extra processing per packet |
| ACK fragmentation | ACK on IP fragments | Reassembly work before the packet can even be classified |
03Why firewalls struggle with it
A purely stateless filter cannot tell an orphaned ACK from a legitimate one, because the only thing that distinguishes them is connection history the filter does not keep. So it either passes everything (useless) or drops ACKs by pattern (breaks real traffic).
A stateful device can tell the difference, but at a price: it has to hold a connection table and look up every packet. Flooding it with orphaned ACKs makes it do that work millions of times a second, and in the SYN-ACK and some ACK cases the packets can even create or churn table entries. The protection mechanism becomes the bottleneck. This is the same trap described for SYN floods, one layer along.
Real incidents show the scale. In April 2024 OVHcloud reported mitigating a record attack of about 840 million packets per second that was 99% TCP ACK flood from roughly 5,000 source addresses, with a 1% DNS reflection component — a packet-rate record driven almost entirely by orphaned ACKs rather than volume.
04How to detect an ACK flood
The signature is a high rate of ACK-bearing packets that do not correlate with your own outbound SYN-ACKs or with established sessions in your connection table. Packet captures make it visible quickly. The filters below were syntax-checked and run against a crafted capture on Debian 12.
- Packet rate climbs sharply while bytes-per-packet stays small and payloads are usually empty
- ACKs arrive for destination ports with no listening service, or with sequence numbers that match no session
- SYN-ACKs arrive that you never requested — the reflection tell
- Connection-table or firewall CPU saturates well before the uplink does
# any ACK that is not part of a handshake or teardown
tcpdump -ni eth0 'tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) == 0
and tcp[tcpflags] & tcp-ack != 0'
# classic empty-payload ACK flood (ACK set, zero TCP payload bytes)
tcpdump -ni eth0 'tcp[tcpflags] == tcp-ack
and (ip[2:2] - ((ip[0] & 0x0f) << 2) - ((tcp[12] & 0xf0) >> 2)) == 0'
# PSH-ACK flood
tcpdump -ni eth0 'tcp[tcpflags] & (tcp-push|tcp-ack) == (tcp-push|tcp-ack)
and tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) == 0'
# unsolicited SYN-ACK flood (reflection)
tcpdump -ni eth0 'tcp[tcpflags] & (tcp-syn|tcp-ack) == (tcp-syn|tcp-ack)'05What actually filters it
The durable fix is to decide, cheaply and early, whether a packet belongs to a real connection, and to drop the ones that do not before they reach anything expensive. Connection tracking does exactly this. Depending on its settings, a flagged packet that matches no tracked flow is either marked invalid or shows up as a "new" flow that did not begin with a SYN — and both can be dropped.
The ruleset below was syntax-checked and load-tested in an isolated network namespace. A normal three-way handshake completed through it, while injected stray ACK, PSH-ACK and SYN-ACK packets were all dropped, both with conntrack's mid-stream pickup on (the default) and off. It is a textbook example, not a tuned production policy.
- Make orphaned-packet handling cheap: drop packets that match no flow as early as possible, before routing, sockets or anything else that costs CPU or allocates state
- Consider setting `net.netfilter.nf_conntrack_tcp_loose = 0` (the Linux default is 1) so the tracker stops adopting mid-stream flows it never saw open; with pickup on, every orphaned ACK that is not dropped can create a fresh table entry. The trade-off is that established sessions break after a conntrack flush or a firewall failover
- Rate-shape per source where sources are not spoofed, but expect reflection variants where they are
- For anything at volume, filter upstream on a network with the packet-per-second headroom to absorb it — a host alone cannot, as explained in how to stop a DDoS attack
table inet ack_guard {
chain prerouting {
type filter hook prerouting priority -150; policy accept;
# no matching connection: stray SYN-ACK and RST (stray ACK too if tcp_loose=0)
ct state invalid counter drop
# a NEW flow must open with a bare SYN; a 'new' ACK is an orphan
tcp flags & (fin|syn|rst|ack) != syn ct state new counter drop
}
}06What a reverse proxy can and cannot do
For web traffic, a reverse proxy terminates TCP for you. The attacker's orphaned ACKs hit the proxy's network, not your origin, and a platform built for it drops them with the packet-rate capacity the job needs. A reverse proxy such as GuardX absorbs the flood upstream so the origin only ever sees completed, legitimate connections.
The honest limits: this only covers traffic that reaches you through the proxy. If your real server address is exposed, an attacker can send ACKs straight to it and bypass the proxy entirely — so hiding the origin matters, as covered in how to hide your origin IP. And for non-HTTP services the proxy model does not apply; those need routed Layer 3/4 filtering, the model behind GRE transit, which is coming soon. Edge filtering also does nothing about an orphaned-ACK flood aimed at an unprotected address you forgot to firewall.
Questions
Frequently asked questions
Sources
Sources and verification
tcpdump BPF filters validated with tcpdump 4.99.3 against a crafted pcap, and the nftables ruleset syntax-checked and load-tested with nft 1.0.6 in an isolated network namespace, on Debian 12, October 2026.
Last updated