Response Rate Limiting
DNS Response Rate Limiting (RRL)
Nexus GSLB's RRL protects against DNS amplification and DDoS attacks by enforcing a per-source-IP query rate limit. It is independent of the global licensed RPS cap — a single abusive IP exhausts only its own bucket and does not affect other clients.
License: DNS response rate limiting requires the pro tier or above. It is disabled at startup on free and paid installations. Contact licensing@gslb.nexus to upgrade.
How it works
Each source IP gets a token bucket. The bucket refills at
perSourceQPS tokens per second up to
burstSize. One token is consumed per query. When a source
IP's bucket is empty, the query is either truncated (TC=1) or dropped
depending on the configured action.
Allowlisted CIDRs bypass rate limiting entirely — no bucket is allocated and no tokens are consumed. Use this for internal networks, monitoring systems, and scheduled jobs that legitimately send high query volumes.
Relationship to the global RPS limit
Inbound query
│
▼
Global RPS check (license gate) ← SERVFAIL if server over licensed capacity
│
▼
Per-source RRL check ← TC=1 or drop if this IP is flooding
│
▼
Normal DNS resolution
The two limits compose but are independent. The RRL catches single-source floods long before they push the server over its global capacity.
TCP behaviour
When the action is truncate and the query arrives over
TCP, Nexus drops the query silently instead of sending TC=1 (TC on TCP
is meaningless and confuses some resolvers). Use
action: drop if you want consistent behaviour across both
transports.
Configuration
dns:
rrl:
enabled: true
perSourceQPS: 100 # tokens refilled per second per source IP
burstSize: 200 # bucket depth — allows bursts above perSourceQPS
action: truncate # "truncate" (TC=1) or "drop"
cleanupInterval: 60s # how often idle source-IP buckets are evicted
allowlist:
- 10.0.0.0/8 # internal network — unrestricted
- 192.168.0.0/16
- 203.0.113.42/32 # specific monitoring hostFields
| Field | Default | Description |
|---|---|---|
enabled |
false |
Enable RRL |
perSourceQPS |
100 |
Token refill rate per source IP (queries/sec) |
burstSize |
2 × perSourceQPS |
Maximum bucket depth; allows short bursts |
action |
truncate |
What to do when a bucket is empty: truncate sends TC=1
(forces TCP retry); drop discards the query silently |
cleanupInterval |
60s |
How often the background goroutine evicts buckets from IPs that have gone quiet |
allowlist |
[] |
CIDR ranges exempt from rate limiting |
Sizing guidance
| Scenario | Suggested settings |
|---|---|
| Public authoritative server, general use | perSourceQPS: 50, burstSize: 100 |
| Internal resolver with scheduled jobs | perSourceQPS: 500, burstSize: 1000,
allowlist internal ranges |
| Aggressive DDoS protection | perSourceQPS: 20, burstSize: 40,
action: drop |
| Monitoring / canary queries only | perSourceQPS: 10, allowlist monitoring CIDRs |
Allowlist for scheduled jobs
Any process that starts up and immediately resolves a large number of names (service discovery, config management, CI/CD) can hit the per-source limit during its startup burst. Add its source CIDR to the allowlist:
dns:
rrl:
enabled: true
perSourceQPS: 100
allowlist:
- 10.0.0.0/8 # all internal servers — unrestricted
- 172.16.50.0/24 # CI runner poolAllowlisted sources do not allocate a token bucket and impose zero overhead on the hot path.
Managing the allowlist at runtime
The allowlist can be managed without editing the config file via the
REST API, the gslbctl CLI, or the Web UI. Entries stored
this way are persisted in the database and merged with any CIDRs in the
config file at daemon startup.
Changes require a daemon restart to take effect. Entries written via the API are saved immediately but the in-memory RRL does not reload mid-flight.
gslbctl
# List current allowlist entries
gslbctl rrl list
# Add a CIDR
gslbctl rrl add 10.0.0.0/8 --description "Internal network"
# Remove an entry by ID (use 'rrl list' to find the ID)
gslbctl rrl remove <id>REST API
| Method | Path | Permission |
|---|---|---|
GET |
/api/v1/dns/rrl/allowlist |
rrl:read |
POST |
/api/v1/dns/rrl/allowlist |
rrl:write |
DELETE |
/api/v1/dns/rrl/allowlist/{id} |
rrl:write |
POST body:
{ "cidr": "10.0.0.0/8", "description": "Internal network" }Web UI
Navigate to Admin → RRL Allowlist. Entries are
visible to users with the rrl:read permission;
rrl:write is required to add or remove entries.
How config-file and DB entries merge
At startup, gslbd loads CIDRs from both sources:
- CIDRs in
dns.rrl.allowlist(config file) - Entries stored in the database via the API
All CIDRs from both sources are combined and passed to the RRL engine. There is no deduplication concern — a CIDR present in both sources is simply matched twice, with the same effect.
Memory usage
Each unique source IP that has sent at least one query since the last
cleanup holds one bucket (~64 bytes). At 1 million unique source IPs
between cleanups: ~64 MB. The cleanup goroutine evicts any IP that has
been quiet for longer than cleanupInterval, so memory is
bounded by the number of active sources, not the total number
of clients ever seen.