Your BIND logs show denied AXFR or IXFR requests from unknown source IPs. The entries appear under the security category at error severity:
client 198.51.100.42#53124: zone transfer 'example.com/AXFR/IN' denied
Denied attempts mean your ACL is working. The critical question is whether any unauthorized transfer succeeded, because a successful AXFR exfiltrates the entire zone: every record, internal hostnames, SRV targets, TXT metadata, and the full infrastructure topology.
Repeated denied attempts from unauthorized sources are reconnaissance, which warrants a ticket. A confirmed successful unauthorized transfer means your zone data has been exfiltrated, which is a page.
What this means
Zone transfers (AXFR for full, IXFR for incremental) are the DNS replication mechanism. They send the complete zone contents over TCP port 53. BIND controls who can request a transfer with the allow-transfer directive, which accepts an address match list: IP ranges, ACL names, or TSIG key references.
When a source not on the allow-transfer list requests a transfer, BIND denies it and logs the denial under security. The source receives a REFUSED response and no data leaves the server.
The danger is twofold. First, a misconfigured allow-transfer that is too permissive lets attackers pull the full zone with no security log entry (because nothing was denied). The transfer would still appear in xfer-out, but only if you collect that category. Second, even denied attempts are reconnaissance: an attacker enumerating your infrastructure before targeting specific hosts revealed by the zone data.
BIND’s default allow-transfer value differs across versions. BIND 9.18 (current ESV) defaults allow-transfer to {any;}, meaning transfers are permitted from any source unless explicitly restricted. BIND 9.20 reportedly changed this default to {none;}, requiring an explicit ACL to enable outgoing transfers at all. If you are upgrading from 9.18 to 9.20 and rely on the old default, transfers will silently stop working for legitimate secondaries.
Common causes
| Cause | What it looks like | First thing to check |
|---|---|---|
| Internet reconnaissance | Scattered denied attempts from many unrelated IPs, single attempts per source | Verify no successful transfers in xfer-out logs |
| Overly permissive ACL | allow-transfer { any; } or missing directive on 9.18 | Run named-checkconf -p and grep for allow-transfer |
| TSIG key drift | Legitimate secondary denied despite correct IP, key mismatch on one side | Compare key values on primary and secondary |
| View inheritance gap | Transfers work from some clients but not others, or fail entirely within views | Check allow-transfer in each view, not just options |
| Stale IP in ACL | Secondary moved to new IP, old IP still listed, new IP not added | Compare current secondary IPs against allow-transfer entries |
Quick checks
# Check for denied zone transfer attempts in the security log
grep -i "denied\|refused" /var/log/named/security.log | grep -i "transfer\|AXFR\|IXFR" | tail -20
# Check for successful outgoing transfers (xfer-out category)
grep -i "transfer of" /var/log/named/xfer.log | tail -20
# Verify allow-transfer configuration in the active config
named-checkconf -p /etc/named.conf | grep -A5 "allow-transfer"
# Check zone transfer statistics counters (replace port with your statistics-channel port)
curl -s http://localhost:8653/json/v1/server | \
python3 -c "import sys,json; d=json.load(sys.stdin); \
[print(f'{k}: {v}') for k,v in d.get('zonestats',{}).items() if 'Xfr' in k]"
# Check TCP connections to port 53 (transfers use TCP)
ss -tn state established '( sport = :53 )' | head -20
# Check BIND version to know your allow-transfer default
rndc status | head -1
How to diagnose it
flowchart TD
A["Denied AXFR/IXFR in security log"] --> B{"Successful transfer
also in xfer-out?"}
B -->|No| C["Reconnaissance only
Severity: TICKET"]
B -->|Yes| D["Zone exfiltrated
Severity: PAGE"]
C --> E["Verify ACL from
unauthorized source"]
E --> F{"Transfer blocked?"}
F -->|Yes| G["ACL working correctly
Log and monitor"]
F -->|No| H["ACL misconfigured
Lock down now"]
D --> I["Incident response
Assume full zone disclosure"]Step 1: Determine whether any unauthorized transfer succeeded.
Denied attempts are logged under security. Successful outgoing transfers are logged under xfer-out. Check both. If your logging configuration only captures xfer-in and xfer-out, you will see successful transfers but miss the denied attempts. If you only collect security, you will see denials but miss successful transfers, including unauthorized ones.
# Check for any successful transfers from unexpected sources
grep -i "transfer of" /var/log/named/xfer.log | grep -v "known-secondary-ip" | tail -20
The statistics channel also tracks zone transfer outcomes. The XfrSuccess counter increments on each successful transfer. Compare its delta against your expected transfer schedule to spot unexpected transfers.
Step 2: Verify the ACL from an unauthorized source.
This is the most commonly skipped step and the most commonly mis-tested. Running dig @127.0.0.1 example.com AXFR from localhost may succeed because localhost is frequently included in allow-transfer for operational convenience. A successful localhost transfer proves nothing about your external posture.
Test from a known-unauthorized source:
# From an external host that is NOT in your allow-transfer list
dig @<your-server-ip> example.com AXFR +time=5 +tries=1
# Expected from a locked-down server: REFUSED, or a connection that
# starts but returns no records
If this succeeds and returns zone records, your zone data is exposed. Treat it as a page-level incident.
Step 3: Check for view inheritance issues.
If you use views (split-horizon DNS), allow-transfer set at the options level may not apply to zones inside views that override it. Each view must have its own allow-transfer statement. A global options-level setting alone is not reliable when views are in use.
# Check allow-transfer in every view, not just options
named-checkconf -p /etc/named.conf | grep -B2 -A5 "allow-transfer"
Step 4: Check TSIG configuration.
If you use TSIG keys for transfer authorization, verify the key exists on both sides and matches. A legitimate secondary with a stale key will be denied, and an attacker without the key is also denied. The security log does not distinguish between these cases by default.
# Verify TSIG key configuration
named-checkconf -p /etc/named.conf | grep -A3 "key.*transfer\|allow-transfer.*key"
# Check key files exist and are readable by named
ls -la /etc/named/*.key /etc/bind/*.key 2>/dev/null
Metrics and signals to monitor
| Signal | Why it matters | Warning sign |
|---|---|---|
| Security log denials (AXFR/IXFR) | Direct evidence of transfer attempts | Sustained or increasing rate from new sources |
| xfer-out log entries | Successful outgoing transfers | Transfers from IPs not in your secondary list |
| XfrSuccess counter (zonestats) | Cumulative count of successful transfers | Unexpected delta outside scheduled transfer windows |
| TCP connection count on port 53 | Zone transfers use TCP | Sustained elevated TCP from a single source |
| Protocol distribution (QryTCP vs QryUDP) | TCP share elevation signals transfers or attacks | TCP share above 5% without known transfer activity |
| tcp-clients utilization | Each transfer consumes a TCP slot | Near the tcp-clients limit (default 150) during transfer windows |
Fixes
Lock down allow-transfer
If allow-transfer is unset and you run BIND 9.18, it defaults to {any;}. Set it explicitly to only your known secondaries:
options {
allow-transfer { 192.0.2.10; 192.0.2.11; };
};
On BIND 9.20, the default is {none;}, so transfers are blocked unless you explicitly enable them. If you are upgrading from 9.18, add explicit allow-transfer statements for each zone or view that should allow transfers to known secondaries, or they will stop working silently.
If you use views, set allow-transfer in each view that serves authoritative zones. Do not rely on a global options-level statement alone.
Add TSIG authentication
IP-based ACLs are fragile: secondaries change IPs, NAT obscures source addresses, and an attacker who can spoof the secondary’s source IP can attempt a transfer. TSIG adds HMAC-based cryptographic authentication to transfer requests:
// On both primary and secondary, define the same key:
key "transfer-key" {
algorithm hmac-sha256;
secret "base64-encoded-key-here";
};
// On the primary, restrict transfers to requests signed with the key:
allow-transfer { key "transfer-key"; };
// On the secondary, associate the key with the primary server:
server 192.0.2.1 { keys { "transfer-key"; }; };
With TSIG, a transfer request must be signed with the correct key. An attacker who knows the secondary’s IP but not the shared secret gets denied.
Collect the right log categories
Denied transfers are logged under security, not under xfer-in or xfer-out. If your logging configuration only captures xfer-in and xfer-out, you will miss denied transfer attempts entirely. Configure logging for all three categories:
logging {
channel security_file {
file "/var/log/named/security.log" versions 3 size 10m;
severity info;
print-time yes;
print-category yes;
print-severity yes;
};
category security { security_file; };
category xfer-in { security_file; };
category xfer-out { security_file; };
};
If you parse logs programmatically, verify your filter patterns against your BIND version’s category structure after upgrading.
Remove deprecated transfer configuration before upgrading
If you are moving to BIND 9.20, the alt-transfer-source, alt-transfer-source-v6, and use-alt-transfer-source statements have reportedly been removed entirely. Using them in 9.20 is a fatal configuration error that prevents named from starting. Remove them from your configuration before the upgrade.
Prevention
- Default to deny. On BIND 9.18, explicitly set
allow-transfer { none; };inoptionsand enable per-zone or per-view only where needed. This matches the 9.20 default and prevents surprises during upgrades. - Use TSIG, not just IPs. IP-based ACLs are spoofable and brittle when infrastructure changes. TSIG keys provide cryptographic proof of identity for transfer requests.
- Test from outside. Verify the ACL from a known-unauthorized source after every configuration change. The localhost test is insufficient and misleading.
- Monitor the security log. Set up alerts on denied AXFR/IXFR attempts. A sudden increase in attempts from new source ranges may indicate targeted reconnaissance.
- Track transfer counters. Baseline your expected transfer count. Any unexpected delta warrants investigation.
- Audit after infrastructure changes. When a secondary changes IP, update
allow-transferon the primary. Stale IPs in the ACL create both false denials and security gaps when old ranges are reassigned.
How Netdata helps
- TCP connection patterns on port 53. Netdata collects TCP connection state distributions per port. A spike in established TCP connections to port 53 from a single source, outside normal transfer windows, correlates with zone transfer activity.
- Protocol distribution monitoring. The ratio of TCP to UDP DNS traffic shifts when transfers occur. Netdata surfaces
QryTCPandQryUDPcounters from the statistics channel, making an unexpected TCP share immediately visible alongside other DNS metrics. - Zone transfer counter tracking. Transfer success and failure counters from the statistics channel are collected as time series. A sudden increase in successful transfers, especially outside scheduled refresh windows, is an early indicator of unauthorized exfiltration.
- Per-second granularity for correlation. When a denied transfer attempt appears in logs, correlating the timestamp with TCP connection patterns, protocol distribution shifts, and transfer counter deltas in a single per-second timeline narrows the investigation to a specific source and time.
- Anomaly detection on query patterns. Netdata’s ML anomaly detection flags unusual shifts in query type distribution, TCP share, or connection patterns that may indicate reconnaissance activity before it escalates to a successful transfer.
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 CPU saturation: single-core bottlenecks, DNSSEC crypto, and per-thread contention
- BIND DNSSEC failing from clock drift: NTP, RRSIG inception/expiry windows, and SERVFAIL
- BIND dnssec-validation disabled: the security regression that ‘fixes’ 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 inline signing silently failed: missing keys and a zone served unsigned






