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-clients option (default 1000) caps concurrent recursive queries, but if the FD limit is lower than what recursive-clients allows, FDs exhaust first.
  • TCP client connections: each accepted TCP connection to port 53 holds an FD until closed. The tcp-clients option (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 .jnl journal.
  • Log files: each configured logging channel that writes to a file holds an FD.
  • Control channel: the rndc listener 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 files
  • socket: file descriptor exceeds limit (N/M) where N is current usage and M is the limit
  • accept: file descriptor exceeds limit
  • could 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 --> B

Common causes

CauseWhat it looks likeFirst thing to check
Default ulimit too low (1024)Works under low load, fails under stress; FD usage near 100% of 1024 limitgrep "Max open files" /proc/$(pgrep -x named)/limits
systemd LimitNOFILE not set/etc/security/limits.conf was configured but named still has low limitsystemctl show named | grep LimitNOFILE
TCP connection accumulationTCP connections to port 53 rising; transfers or large responses failingss -tan '( sport = :53 )' | wc -l
Zone transfer burstMany secondaries transferring simultaneously; FD spike during refresh windowsBIND logs category xfer-in / xfer-out
Traffic growth exceeding capacityFD usage trending upward over weeks; daily peak approaching 50% of limitHistorical 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

  1. Confirm FD usage is near the limit. Run the FD count and limit check above. Above 70% of Max open files is the warning zone. Above 90% is critical. At 100%, BIND is actively failing to open sockets.

  2. Identify what is consuming FDs. Examine /proc/$PID/fd to see the breakdown. Socket FDs (shown as socket:[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 high RecursClients points to recursive query load.

  3. Check whether systemd or limits.conf is the binding constraint. systemd’s LimitNOFILE takes precedence over /etc/security/limits.conf for services. If you set limits in limits.conf but did not configure the systemd unit, named still runs with the systemd default. Verify with systemctl show named | grep LimitNOFILE.

  4. Check the BIND version for the files option. The files option in named.conf is deprecated in recent BIND releases. If named rejects the configuration on startup after an upgrade, remove the files directive and set FD limits at the OS level.

  5. Look for QuerySockFail in resolver statistics. This counter tracks failures opening query sockets for outbound recursive queries. A non-zero or increasing QuerySockFail rate is direct evidence that FD exhaustion is affecting recursive resolution.

  6. 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

SignalWhy it mattersWarning sign
FD count as percentage of Max open filesDirect measure of resource exhaustionAbove 70% alert, above 90% page
TCP connection count on port 53TCP connections are FD-heavy; accumulation drives exhaustionSustained count near tcp-clients limit (default 150)
RecursClients as percentage of recursive-clientsEach in-flight recursive query holds FDsAbove 50% sustained with rising FD usage
QuerySockFail (per-view resolver stat)Direct evidence of socket creation failures from FD limitsAny non-zero rate
rndc status response timeControl channel uses FDs; degrades under pressureResponse time exceeding 5 seconds
QrySERVFAIL rateCollateral damage when BIND cannot process queriesSpike correlating with high FD usage
QryTCP vs QryUDP ratioElevated TCP share increases FD pressureTCP 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-on addresses. 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-clients slots and exhausts FDs. Consider rate-limit configuration 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 LimitNOFILE to at least 65536 in the systemd unit for every production named instance. Verify after deployment with grep "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 files option from named.conf on 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 RecursClients and 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 LimitNOFILE to the default.

How Netdata helps

  • Per-second FD usage collection: Netdata’s apps or users plugin 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 QuerySockFail catch 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 rndc response time to build alert conditions that distinguish FD exhaustion from other causes of query drops.