BIND logs “too many open files” or “socket: file descriptor exceeds limit” and starts dropping queries. UDP health checks may still pass. Zone transfers fail intermittently. The rndc control channel becomes sluggish or unresponsive. Clients see random timeouts that look like upstream nameserver problems. This is file descriptor exhaustion, and the symptoms masquerade as network or disk issues.
The default ulimit -n of 1024 on many Linux distributions is low for a production DNS server. BIND consumes file descriptors (FDs) for every listener socket, outbound recursive query socket, TCP client connection, zone file, journal file, log file, and the rndc control channel. Under moderate load, a recursive resolver can exhaust 1024 FDs in minutes.
The failure is cliff-edge. There is no graceful degradation. BIND is processing queries normally, then it cannot open new sockets and silently drops queries or fails transfers. UDP-only monitoring stays green because existing listener sockets remain open. The problem becomes visible only when you check FD usage against the limit or notice that TCP-dependent operations (zone transfers, large responses) are failing.
What this means
BIND’s FD consumption scales with:
- Listener sockets: one UDP and one TCP socket per configured listen address, per IP version.
- Outbound recursive sockets: each in-flight recursive query to an upstream nameserver holds at least one FD. The
recursive-clientsoption (default 1000) caps concurrent recursive queries, but if the FD limit is lower than whatrecursive-clientsallows, FDs exhaust first. - TCP client connections: each accepted TCP connection to port 53 holds an FD until closed. The
tcp-clientsoption (default 150) limits concurrent TCP connections. - Zone and journal files: each loaded zone with dynamic updates or inline signing opens file handles for the zone file and its
.jnljournal. - Log files: each configured logging channel that writes to a file holds an FD.
- Control channel: the
rndclistener on TCP port 953 holds an FD.
When the total exceeds Max open files, BIND cannot create new sockets or open new files. The kernel denies the socket(), accept(), or open() syscall. BIND logs one of several messages:
too many open filessocket: file descriptor exceeds limit (N/M)where N is current usage and M is the limitaccept: file descriptor exceeds limitcould not listen on UDP socket: not enough free resource
Not every failure produces a log line. Queries dropped during processing because BIND could not allocate a socket for the response may vanish silently. The gap between what BIND logs and what actually happens can be large.
flowchart TD
A["Query arrives at named"] --> B{"FD available?"}
B -- No --> C["Cannot open socket"]
C --> D["Query silently dropped
No log entry"]
C --> E["Transfer fails
Error logged"]
C --> F["rndc degraded
or unresponsive"]
B -- Yes --> G["Normal processing"]
G --> H["FD held for
duration of operation"]
H --> I["Operation completes
FD released"]
H --> J{"More queries
than FDs released?"}
J -- Yes --> K["FD count climbs
toward limit"]
K --> BCommon causes
| Cause | What it looks like | First thing to check |
|---|---|---|
| Default ulimit too low (1024) | Works under low load, fails under stress; FD usage near 100% of 1024 limit | grep "Max open files" /proc/$(pgrep -x named)/limits |
| systemd LimitNOFILE not set | /etc/security/limits.conf was configured but named still has low limit | systemctl show named | grep LimitNOFILE |
| TCP connection accumulation | TCP connections to port 53 rising; transfers or large responses failing | ss -tan '( sport = :53 )' | wc -l |
| Zone transfer burst | Many secondaries transferring simultaneously; FD spike during refresh windows | BIND logs category xfer-in / xfer-out |
| Traffic growth exceeding capacity | FD usage trending upward over weeks; daily peak approaching 50% of limit | Historical FD usage trend |
| FD leak (rare, version-specific) | FD count grows monotonically without release; never stabilizes | /proc/<pid>/fd count over time |
Quick checks
Run these read-only commands to confirm or rule out FD exhaustion. The service name may be named or bind9 depending on distribution. If multiple named processes are running, pgrep -x named returns the first match; specify the PID explicitly instead.
# Check current FD count vs limit
PID=$(pgrep -x named)
CURRENT=$(ls /proc/$PID/fd 2>/dev/null | wc -l)
MAX=$(grep "Max open files" /proc/$PID/limits | awk '{print $4}')
echo "Using $CURRENT / $MAX file descriptors ($(( CURRENT * 100 / MAX ))%)"
# Check what named has open (socket targets, file paths)
ls -l /proc/$PID/fd 2>/dev/null | awk '{print $NF}' | sed 's/\[.*\]//' | sort | uniq -c | sort -rn | head -20
# Check for FD exhaustion errors in recent logs
journalctl -u named --since "1 hour ago" | grep -i "too many open files\|file descriptor exceeds limit\|not enough free resource"
# Check systemd's FD limit for named (overrides limits.conf for services)
systemctl show named | grep LimitNOFILE
# Check TCP connection count on port 53
ss -tan '( sport = :53 )' | tail -n +2 | awk '{print $1}' | sort | uniq -c
# Check QuerySockFail counter (socket errors on outbound queries)
# Requires a configured statistics-channels block in named.conf.
curl -s http://localhost:8653/json/v1/server | \
python3 -c "import sys,json; d=json.load(sys.stdin); \
[print(f'{v}: {k}: {s}') for v,vd in d.get('views',{}).items() \
for k,s in sorted(vd.get('resolver',{}).get('stats',{}).items()) \
if k == 'QuerySockFail']"
# Check recursive clients (each holds at least one FD)
curl -s http://localhost:8653/json/v1/server | \
python3 -c "import sys,json; d=json.load(sys.stdin); \
print('RecursClients:', d.get('nsstats',{}).get('RecursClients', 'N/A'))"
# Check rndc responsiveness (degrades under FD pressure)
timeout 5 rndc status >/dev/null 2>&1 && echo "rndc OK" || echo "rndc SLOW/FAIL"
# Functional TCP test (fails when FDs exhausted, UDP may still work)
dig +tcp +time=2 +tries=1 @127.0.0.1 example.com A
How to diagnose it
Confirm FD usage is near the limit. Run the FD count and limit check above. Above 70% of
Max open filesis the warning zone. Above 90% is critical. At 100%, BIND is actively failing to open sockets.Identify what is consuming FDs. Examine
/proc/$PID/fdto see the breakdown. Socket FDs (shown assocket:[NNN]) indicate listener, recursive, or TCP connections. Regular file paths indicate zone files, journals, or logs. A large count of socket FDs with many ESTABLISHED TCP connections points to TCP accumulation. A large count with highRecursClientspoints to recursive query load.Check whether systemd or limits.conf is the binding constraint. systemd’s
LimitNOFILEtakes precedence over/etc/security/limits.conffor services. If you set limits inlimits.confbut did not configure the systemd unit, named still runs with the systemd default. Verify withsystemctl show named | grep LimitNOFILE.Check the BIND version for the
filesoption. Thefilesoption innamed.confis deprecated in recent BIND releases. If named rejects the configuration on startup after an upgrade, remove thefilesdirective and set FD limits at the OS level.Look for
QuerySockFailin resolver statistics. This counter tracks failures opening query sockets for outbound recursive queries. A non-zero or increasingQuerySockFailrate is direct evidence that FD exhaustion is affecting recursive resolution.Test UDP vs TCP independently. If UDP queries work but TCP queries fail, FD exhaustion is likely. UDP listener sockets are long-lived and remain open. TCP connections require a new FD per accepted connection. When FDs are scarce, TCP fails first.
Metrics and signals to monitor
| Signal | Why it matters | Warning sign |
|---|---|---|
FD count as percentage of Max open files | Direct measure of resource exhaustion | Above 70% alert, above 90% page |
| TCP connection count on port 53 | TCP connections are FD-heavy; accumulation drives exhaustion | Sustained count near tcp-clients limit (default 150) |
RecursClients as percentage of recursive-clients | Each in-flight recursive query holds FDs | Above 50% sustained with rising FD usage |
QuerySockFail (per-view resolver stat) | Direct evidence of socket creation failures from FD limits | Any non-zero rate |
rndc status response time | Control channel uses FDs; degrades under pressure | Response time exceeding 5 seconds |
| QrySERVFAIL rate | Collateral damage when BIND cannot process queries | Spike correlating with high FD usage |
| QryTCP vs QryUDP ratio | Elevated TCP share increases FD pressure | TCP share above 5% of total queries |
Fixes
Increase the OS file descriptor limit
The primary fix. On systemd-managed systems, create or edit the service override:
# Create systemd override for named
systemctl edit named
Add or modify:
[Service]
LimitNOFILE=65536
Then reload systemd and restart named:
systemctl daemon-reload
systemctl restart named
This restart interrupts DNS service briefly. Schedule accordingly.
For non-systemd systems, set limits in /etc/security/limits.conf:
named soft nofile 65536
named hard nofile 65536
The process must be restarted for the new limit to take effect. As root, you can raise limits on a running process with prlimit --pid <pid> --nofile=<new_soft>:<new_hard> as a temporary measure, but restart afterward so the systemd unit or limits.conf remains the source of truth.
Production BIND servers should run with at least 65536 FDs. High-traffic recursive resolvers or authoritative servers with many zones may need 1048576 or more.
Remove the deprecated files option from named.conf
The files option in named.conf has no effect on recent BIND versions and may cause a configuration error. Remove any files directive from named.conf and rely entirely on OS-level limits.
Reduce FD consumers if the limit cannot be raised
If you cannot raise the FD limit (container constraints, shared host), reduce FD consumption:
- Lower
tcp-clients(default 150). Each concurrent TCP connection holds an FD. Reducing this limits TCP FD consumption but may cause legitimate TCP queries to be refused. - Lower
recursive-clients(default 1000). Each in-flight recursive query holds FDs. Reducing this cap limits recursive FD usage but causes SERVFAIL when the lower limit is reached. - Reduce the number of
listen-onaddresses. Each listen address consumes FDs for both UDP and TCP listeners. Consolidate where possible. - Disable query logging if enabled. Each open log file channel holds FDs, and query logging at high QPS generates excessive I/O that compounds the problem.
These are tradeoffs, not fixes. The right answer is to raise the FD limit to match the workload.
Address TCP-based attacks or transfer storms
If FD consumption is driven by abnormal TCP patterns:
- A TCP SYN flood or slow-loris-style attack fills
tcp-clientsslots and exhausts FDs. Considerrate-limitconfiguration or upstream firewall rules to throttle TCP connection rates. - A zone transfer storm (many secondaries transferring simultaneously) is normal during refresh windows but can exhaust FDs on a busy server. Stagger secondary refresh schedules if possible.
Prevention
- Set
LimitNOFILEto at least 65536 in the systemd unit for every production named instance. Verify after deployment withgrep "Max open files" /proc/$(pgrep -x named)/limits. - Monitor FD usage as a percentage of limit. Alert at 70%, page at 90%. Track the daily peak. If the daily peak exceeds 50% of the limit, plan to increase the limit or add capacity before the next traffic spike.
- Remove the
filesoption fromnamed.confon all BIND 9.18+ installations. - Test both UDP and TCP in health checks. UDP-only checks miss FD exhaustion because UDP listener sockets are persistent. TCP queries fail first when FDs are scarce.
- Correlate FD usage with
RecursClientsand TCP connection count. These are the two largest FD consumers. If either trends upward, FD pressure follows. - Verify the limit after every deployment or config change. A package update or systemd unit change can silently reset
LimitNOFILEto the default.
How Netdata helps
- Per-second FD usage collection: Netdata’s
appsorusersplugin tracks open file descriptors per process, giving you a high-resolution view of FD consumption that catches spikes that 60-second polling misses. - FD limit correlation: Netdata collects process limits alongside usage, so you see the ratio directly rather than computing it manually.
- BIND statistics channel integration: Netdata’s BIND collector pulls
RecursClients,QrySERVFAIL,QuerySockFail, and socket statistics per second. When FD usage spikes, you can immediately see whether recursive clients, TCP connections, or socket failures drove it. - TCP connection state tracking: Netdata monitors ESTABLISHED, TIME_WAIT, and other TCP states on port 53, making it easy to distinguish transfer bursts from attacks.
- Anomaly detection: ML-based anomaly flags on FD count and
QuerySockFailcatch slow FD leaks and gradual consumption growth before they hit the cliff edge. - Composite alerting: Correlate FD usage with SERVFAIL rate, recursive client count, and
rndcresponse time to build alert conditions that distinguish FD exhaustion from other causes of query drops.
Related guides
- BIND DNSSEC validation failing: ‘broken trust chain’, ValFail, and SERVFAIL for signed domains
- BIND cache eviction storms: DeleteLRU, an undersized max-cache-size, and the pressure spiral
- BIND cache hit ratio dropping: the leading edge of recursive pain
- BIND clients-per-query and max-clients-per-query: duplicate recursion for popular names
- BIND cold cache after restart: the warming storm and elevated upstream load
- BIND DNSSEC failing from clock drift: NTP, RRSIG inception/expiry windows, and SERVFAIL
- BIND dynamic update failures: UpdateFail, denied updates, and TSIG drift
- BIND forwarding loops: recursion that never terminates and burns recursive slots
- How BIND actually works in production: a mental model for operators
- BIND journal (.jnl) corruption: dynamic-update and IXFR failures that block zone load
- BIND lame delegations: ’lame server resolving’ and nameservers that are not authoritative
- BIND managed-keys and trust anchors: KSK rollover, RFC 5011, and a stale root key






