A secondary BIND server’s zone transfers are failing. The zone is still being served, clients are still getting answers, and your health checks are still green. The secondary is serving stale data from the last successful transfer, and the SOA expire timer is counting down. When it reaches zero, the secondary stops serving the zone and returns SERVFAIL or REFUSED for every query.

Depending on the zone’s SOA values, you may have hours, days, or weeks of runway before the zone goes dark, with no visibility into the countdown unless you are specifically tracking it.

This article covers how to read the xfer-in log category, diagnose why transfers are failing, and compute your actual expire runway before the zone disappears.

What this means

BIND uses two zone transfer mechanisms, both over TCP port 53:

  • AXFR (full transfer): the entire zone is sent from primary to secondary. Used for the initial load and as a fallback when IXFR fails or is unavailable.
  • IXFR (incremental transfer): only the differences between the current serial and the secondary’s serial are sent. Requires a valid journal on both sides.

A secondary requests IXFR by default (request-ixfr yes). If the primary does not support IXFR or cannot compute a delta from its journal, the secondary falls back to AXFR. Occasional fallback is normal. Frequent AXFR fallback indicates a problem: journal corruption, a serial gap too large for IXFR, or the primary lacking sufficient IXFR history.

When a transfer fails, BIND logs it under the xfer-in category. The failure is invisible to clients because the secondary continues serving the zone from its last successful copy. The zone’s SOA record contains the timers that govern this grace period:

  • refresh: how often the secondary checks the primary’s serial
  • retry: how long to wait before retrying after a failed refresh
  • expire: how long the secondary will continue serving stale data before giving up entirely

The expire value is the hard deadline. Once it passes without a successful transfer, the secondary removes the zone.

flowchart TD
    A["Transfer fails
logged in xfer-in"] --> B["Secondary serves
last-known copy"] B --> C["SOA refresh timer
triggers retry"] C --> D{"Transfer succeeds?"} D -- Yes --> E["Zone in sync
serials match"] D -- No --> F["SOA retry timer
triggers another retry"] F --> G{"Expire timer
reached?"} G -- No --> C G -- Yes --> H["Zone removed
SERVFAIL for all queries"]

Common causes

CauseWhat it looks likeFirst thing to check
Primary unreachableConnection timeout or refused in xfer-in logs; primary may be cached as unreachabledig +time=2 @primary-ip zone SOA
Firewall blocking TCP/53UDP SOA queries work but TCP fails; connection reset or timeout on transferdig +tcp +time=2 @primary-ip zone SOA
TSIG key mismatchAuthentication failure or bad signature in xfer-in logsCompare key names, algorithms, and secrets on both servers
Journal corruption on secondaryIXFR repeatedly fails and falls back to AXFR; every refresh does a full transferCheck .jnl file integrity
BIND 9.20.0 allow-transfer defaultAll transfers fail after upgrading the primary; no explicit ACLCheck allow-transfer on the primary
Primary overloaded (tcp-clients)Intermittent failures; primary logs quota exhaustion on TCP clientsCheck tcp-clients limit and concurrent load on primary
Secondary transfer quota fullTransfers deferred, not failed; quota message in xfer-in logsCheck transfers-in (default 10) and transfers-per-ns (default 10)

Quick checks

# Compare SOA serials - third field in each output is the serial
dig @primary-ip example.com SOA +short
dig @127.0.0.1 example.com SOA +short

# Check zone state on the secondary
rndc zonestatus example.com

# Check recent xfer-in log entries
journalctl -u named --since "1 hour ago" | grep -iE "xfer|transfer"

# Test TCP connectivity to primary on port 53
dig +tcp +time=2 +tries=1 @primary-ip example.com SOA

# Check transfer-related counters via statistics channel
# <!-- TODO: verify actual zonestats counter names in BIND JSON stats; XfrFail/XfrSuccess may not exist -->
curl -s http://127.0.0.1:8053/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()]"

# Verify allow-transfer ACL on primary
named-checkconf -p | grep -A3 allow-transfer

# Check current transfer activity on the secondary
rndc status | grep -i transfer

How to diagnose it

  1. Confirm the serial mismatch. Compare the SOA serial on the primary and the secondary. If they differ, the secondary is behind. Use dig @primary-ip zone SOA +short and dig @127.0.0.1 zone SOA +short and compare the third field.

  2. Compute your expire runway. Run rndc zonestatus zone on the secondary. If the zone is close to expiry and you have no other signal, treat any persistent serial mismatch lasting longer than the refresh interval as urgent.

  3. Read the xfer-in logs. The xfer-in log category records incoming transfer attempts and their outcomes. Look for the specific failure mode: connection refused, timeout, authentication failure, FORMERR, or quota deferral. Each points to a different root cause. If you filter by xfer-in and see nothing, broaden the search to general.

  4. Test TCP connectivity to the primary. Zone transfers use TCP. Run dig +tcp +time=2 +tries=1 @primary-ip zone SOA. If this fails but UDP works, the problem is TCP-specific: firewall, tcp-clients exhaustion, or a primary that is not listening on TCP.

  5. Check for the unreachable-primary cache. When a TCP connection to the primary fails, BIND caches the primary as unreachable. During this window, refresh attempts are skipped even if the primary becomes reachable again. A NOTIFY message from the primary clears this cache early. If you just fixed a network issue, either wait for the cache to expire or run rndc retransfer zone to bypass it.

  6. Verify TSIG if configured. If transfers use TSIG authentication, verify that the key name, algorithm, and secret match exactly on both primary and secondary. A common failure is rotating the key on one side but not the other. TSIG is the only supported transaction signature mechanism for zone transfers in current BIND releases.

  7. Check for journal corruption. If transfers succeed but the secondary keeps doing full AXFR instead of IXFR, the secondary’s .jnl file may be corrupted. This forces a full zone transfer on every refresh, which is wasteful and can fail on large zones under transfer quotas.

  8. Check BIND version-specific changes. If transfers started failing after an upgrade:

    • BIND 9.20.0 changed the allow-transfer default. Without an explicit allow-transfer ACL at the zone, view, or options level on the primary, outgoing transfers may be denied.
    • BIND 9.20.0 removed alt-transfer-source, alt-transfer-source-v6, and use-alt-transfer-source. Configurations relying on these will fail named-checkconf.
    • BIND 9.18.0+ performs stricter validation of the question section in AXFR, IXFR, and SOA replies. Transfers from older or non-compliant primaries may produce FORMERR.

Metrics and signals to monitor

SignalWhy it mattersWarning sign
SOA serial mismatch (primary vs secondary)Earliest indicator that a transfer failed or is laggingAny mismatch persisting beyond the refresh interval
SOA expire runwayThe actual countdown to zone removalBelow 50% of expire value; critical below 25% or 24 hours
Transfer failure counters (zonestats)Cumulative count of failed transfers since startupNon-zero and increasing between polls
Transfer success counters (zonestats)Confirms transfers are completingCounter stops incrementing
xfer-in log entriesContains the specific failure reasonAny error-level entry in xfer-in category
TCP connectivity to primary on port 53Transfers require TCP; UDP health checks hide transfer failuresTCP SOA query fails while UDP succeeds
Transfer quota utilizationSecondary defers transfers when limits are reachedQuota deferral messages in xfer-in logs

Fixes

Primary unreachable or firewall blocking TCP/53

Verify the network path with dig +tcp @primary-ip zone SOA. If TCP fails, check firewall rules on the primary and any intermediate network devices. Zone transfers require TCP/53, which is often blocked by default in cloud security groups and corporate firewalls even when UDP/53 is open.

If the primary was unreachable and you fixed the issue, BIND may still have it cached as unreachable. Run rndc retransfer zone on the secondary to force an immediate transfer attempt.

TSIG key mismatch

Compare the key definitions on both servers. The key name, algorithm (typically hmac-sha256), and base64-encoded secret must match exactly. After correcting the key on one side, run rndc retransfer zone on the secondary.

Journal corruption

If the secondary repeatedly falls back from IXFR to AXFR, the .jnl file may be corrupted. To recover:

  1. Stop named on the secondary.
  2. Delete the zone’s .jnl file.
  3. Start named.
  4. Run rndc retransfer zone to force a clean AXFR.

The journal will be rebuilt from the fresh transfer. This is disruptive: stopping named briefly interrupts service for all zones on that server.

BIND 9.20.0 allow-transfer default change

After upgrading the primary to BIND 9.20.0 or later, add an explicit allow-transfer ACL at the zone, view, or options level. If you previously relied on alt-transfer-source for alternate transfer source addresses, migrate to transfer-source and transfer-source-v6.

Primary overloaded

The tcp-clients limit on the primary is shared between TCP client queries and zone transfers. When exhausted, the primary rejects new TCP connections and the secondary sees connection resets. Increase tcp-clients on the primary or reduce concurrent TCP query load.

On the secondary, check transfers-in (default 10) and transfers-per-ns (default 10). When these quotas are full, transfers are deferred, not failed. Increase the limits if the secondary manages many zones from the same primary.

Forcing a transfer

rndc retransfer zone forces a fresh transfer on the secondary. On older versions, rndc retransfer queues a transfer but does not abort a stuck one, so you may need to wait for the current attempt to time out.

After a forced transfer, verify with rndc zonestatus zone that the serial now matches the primary and the expire runway has reset.

Prevention

  • Monitor SOA serial consistency between primary and all secondaries. Compare serials on a schedule shorter than the refresh interval. Any mismatch is the earliest sign of a transfer problem.
  • Track the SOA expire runway as a trend, not a threshold. Alert when runway drops below 50% of the expire value. Page when it drops below 25% or 24 hours, whichever is shorter.
  • Capture xfer-in log entries in your monitoring pipeline. The log category contains the specific failure reason. Alert on error-level entries.
  • Watch transfer failure counters. Any non-zero rate between polls means transfers are failing.
  • Test TCP/53 connectivity independently of UDP. A UDP-only health check will never catch transfer-path failures.
  • Validate configuration before every reload. Run named-checkconf before rndc reload to catch ACL changes and removed directives before they break transfers.
  • After upgrades, verify transfers explicitly. BIND version changes can silently break transfers.

How Netdata helps

  • SOA serial mismatch detection correlates serials between primary and secondary, flagging divergence within minutes instead of waiting for the expire timer to surface the problem.
  • SOA expire runway trending tracks the countdown as a time-series signal. Alerting at 50% and 25% thresholds gives operators hours or days of lead time before the zone goes dark.
  • xfer-in log correlation alongside TCP connectivity checks to the primary identifies whether the failure is network-level, authentication-level, or capacity-level.
  • Transfer counter monitoring surfaces when transfers stop completing and for how long.
  • Composite alerting on failed transfers combined with a shrinking expire runway catches the specific failure pattern that causes silent outages: a zone serving stale data for days before the secondary removes it.