Rate limiting connections per second in nftables is surprisingly effective because it operates at the packet-filtering layer, allowing you to drop unwanted traffic before it even hits your application, saving precious resources.

Let’s see it in action. Imagine you’re running a web server and want to prevent a single IP address from opening more than 10 new TCP connections within any 60-second window.

table ip filter {
    chain input {
        type filter hook input priority 0; policy accept;

        # Rate limit new TCP connections from a single IP to 10 per minute
        tcp dport 80,443 ct state new meter new_conn_rate limit rate over 10/minute burst 20 drop

        # Allow established and related connections
        ct state established,related accept

        # Drop everything else
        drop
    }
}

Here’s the breakdown:

  1. table ip filter { ... }: This defines a table named filter for IPv4 packets. nftables uses tables to group chains.
  2. chain input { ... }: This defines a chain named input that hooks into the input path of the netfilter framework. This means it processes packets destined for the local machine. priority 0 is a standard priority, and policy accept means any packet not explicitly dropped by a rule will be accepted by default.
  3. tcp dport 80,443: This rule specifically targets TCP packets destined for ports 80 (HTTP) and 443 (HTTPS). You can adjust these ports as needed.
  4. ct state new: This is crucial. ct stands for connection tracking. state new matches only packets that are attempting to initiate a new connection (the SYN packet in a TCP handshake). This prevents us from rate-limiting all traffic from an IP, including ongoing communication.
  5. meter new_conn_rate limit rate over 10/minute burst 20: This is the heart of the rate limiting.
    • meter new_conn_rate: We’re defining a named meter called new_conn_rate. Meters are used to track statistics and enforce limits.
    • limit rate over 10/minute: This sets the primary rate limit. The meter will track how many packets matching the preceding criteria (new TCP connections to ports 80/443) arrive. If the rate exceeds 10 packets per minute on average over a short interval, the limit is triggered.
    • burst 20: This is the burst capacity. It allows for short spikes of traffic. If the meter is below its average rate, it can accumulate tokens up to the burst value. So, if an IP has been quiet, it can send up to 20 new connections in quick succession before being throttled, as long as the average rate over the minute stays below 10. This prevents legitimate, but momentarily bursty, traffic from being unfairly dropped.
  6. drop: If the meter detects that the rate limit has been exceeded, this action is taken – the packet is dropped.
  7. ct state established,related accept: This rule is essential for maintaining active connections. It allows packets that are part of an existing connection or are related to an existing connection (like ICMP error messages) to pass through without being subject to the new connection rate limit.
  8. drop: This is a catch-all rule at the end of the chain. Any packet that reaches this point and hasn’t been accepted by a previous rule will be dropped.

The mental model here is that nftables acts as a highly efficient traffic cop at your network interface. For incoming connections to your web services, it first checks if the connection is new. If it is, it consults a "token bucket" (the meter) for that specific source IP. This bucket has a steady refill rate (10 per minute) but can hold a certain number of tokens (burst of 20). If a new connection attempt arrives and there are tokens in the bucket, one is consumed, and the connection is allowed. If the bucket is empty, the connection attempt is dropped. Established connections bypass this check entirely.

The rate over X/minute syntax is quite flexible. You can specify 10/minute, 10/60s, or even 10/10s for a more aggressive, shorter-term limit. The burst value is critical for tuning. A small burst value means very little tolerance for spikes, while a larger one allows for more temporary bursts. For web servers, a burst value of 2-3 times the rate is often a good starting point, allowing for natural user behavior.

A common pitfall is forgetting ct state new. If you omit this, you’ll rate-limit all traffic from an IP, including legitimate ongoing requests, effectively DoS-ing your own users. Another is not having the ct state established,related accept rule, which would break all existing connections.

Once you’ve successfully rate-limited new connections, you might find yourself dealing with excessively large packets or malformed packets that are still getting through.

Want structured learning?

Take the full Nftables course →