BIND’s Response Rate Limiting (RRL) doesn’t just slow down attackers; it actually punishes them by making their attack traffic more expensive for them to send.

Let’s see RRL in action. Imagine a client bombarding your BIND server with requests for a specific, non-existent record, like badhost.example.com. Without RRL, BIND would dutifully respond to every single one, consuming your bandwidth and CPU. With RRL configured, BIND will start dropping or rate-limiting responses to these repeated queries.

Here’s how you’d configure it in named.conf (or a file included by it):

options {
    // ... other options ...
    rate-limit {
        responses-per-second 5; // Limit to 5 responses per second for a given type/name
        window 10s;              // Apply the limit over a 10-second window
        log-only no;             // Actually drop/limit responses, not just log them
        // Optionally, you can add exceptions or different profiles
        // e.g., for specific zones or query types.
    };
    // ... other options ...
};

What this snippet does is apply a global rate limit. For any given client IP and any given QNAME/QTYPE combination, BIND will only send up to 5 responses within a 10-second rolling window. If a client exceeds this, subsequent responses for that specific QNAME/QTYPE from that IP will be dropped.

The magic is in how BIND tracks this. It uses a sliding window approach. For each incoming query, BIND looks at the source IP address and the query details (name and type). It maintains a counter for that specific (source_ip, qname, qtype) tuple. If the counter exceeds the responses-per-second limit within the window, the response is suppressed.

The log-only no; directive is crucial. If it’s yes, BIND will only log that it would have dropped a response, but it will still send it. Setting it to no means BIND will actively drop or rate-limit the outgoing UDP packet.

You can get much more granular. For instance, you might want to be more aggressive with NXDOMAIN responses (which are often used in reflection attacks) than with legitimate, existing records.

zone "example.com" {
    type master;
    file "zones/db.example.com";
    rate-limit {
        nxdomain-responses-per-second 2; // Very aggressive for NXDOMAIN
        nxdomain-window 5s;
        responses-per-second 10;       // Less aggressive for other responses
        window 10s;
        log-only no;
    };
};

In this zone-specific configuration, BIND will limit NXDOMAIN responses for example.com to 2 per 5 seconds, while other response types get a limit of 10 per 10 seconds. This protects example.com from certain types of NXDOMAIN-based amplification attacks without overly impacting legitimate queries for existing records.

The underlying mechanism involves BIND maintaining a hash table of (client_ip, qname, qtype) tuples, each associated with a timestamped list of sent responses. When a new response is about to be sent, BIND checks this list. If sending the response would violate the rate limit (i.e., more than responses-per-second have been sent within the last window), the response is dropped.

One of the most potent RRL configurations involves using the nodata-responses-per-second and nxdomain-responses-per-second directives. These are specifically designed to combat amplification attacks that rely on servers responding to queries for non-existent records. An attacker might send a query for a.nonexistent.example.com to many DNS servers, spoofing the source IP to be the victim’s. Each DNS server responds with an NXDOMAIN or NODATA message, which can be larger than the original query, thus amplifying the traffic towards the victim. By aggressively limiting these specific types of responses, you can effectively neutralize this attack vector.

The most common mistake people make is setting the responses-per-second values too low globally. This can inadvertently block legitimate traffic from recursive resolvers that might have many clients behind a single IP address. It’s often better to start with higher limits and tune them down, or better yet, use zone-specific profiles to apply stricter limits only where necessary.

The next thing you’ll likely encounter is understanding how to differentiate between a legitimate recursive resolver and a malicious actor when applying these limits.

Want structured learning?

Take the full Bind course →