You updated a zone on the primary, reloaded with rndc reload, and confirmed the primary is serving the new serial. The secondaries still show the old one. Queries against them return stale data for minutes or hours. The zone is not broken, just delayed.
This is NOTIFY not reaching the secondaries. NOTIFY (RFC 1996) is a UDP push from the primary telling secondaries to check for a new SOA serial immediately, rather than waiting for the refresh timer. When NOTIFY is lost, blocked, or misconfigured, the secondary learns about changes only when its SOA refresh timer fires (commonly 1 hour for typical SOA values). The data looks delayed, not broken. The refresh timer eventually catches up, which is why this rarely pages, but it can mask more serious transfer problems if the refresh timer is the only thing keeping secondaries current.
What this means
NOTIFY is UDP. It is fire-and-forget by design. The primary sends it, there is no acknowledgment at the protocol level, and if the packet is lost the primary does not retry. The SOA refresh timer is the protocol-level fallback.
When a primary loads or reloads a zone, it sends NOTIFY to all nameservers listed in the zone’s NS records, except itself and the SOA MNAME. It also sends to any IPs listed in also-notify. The default is notify yes, which covers both groups.
On the receiving side, the secondary does not query the notifier directly. It schedules a zone refresh and works through its configured primaries list, sending SOA queries to each until it finds one with a higher serial. Even when NOTIFY arrives, the secondary still needs network access to the primary for the SOA query and the subsequent zone transfer (AXFR or IXFR over TCP 53).
A NOTIFY is considered valid only if the sender is in the zone’s NS RRset, is explicitly allowed via allow-notify on the secondary, or is in the secondary’s primaries list. NOTIFY from unauthorized sources is silently ignored.
flowchart TD
A["Primary reloads zone"] --> B["NOTIFY via UDP 53 to NS + also-notify"]
B --> C{"Reaches secondary?"}
C -->|no| D["Refresh timer is the only fallback"]
C -->|yes| E{"Sender authorized?"}
E -->|no| D
E -->|yes| F["SOA check, then AXFR/IXFR"]
F --> G{"TCP 53 transfer works?"}
G -->|yes| H["Secondary serves new serial"]
G -->|no| DCommon causes
| Cause | What it looks like | First thing to check |
|---|---|---|
| Firewall blocks UDP 53 | NOTIFY never arrives; serials converge only on refresh schedule | tcpdump on secondary for NOTIFY from primary IP |
| Wrong notify mode | notify explicit without also-notify, or notify no in options or per-zone | named-checkconf -p and grep for notify |
| also-notify missing or wrong IPs | Servers not in the NS RRset never receive NOTIFY | Verify all intended targets are in NS records or also-notify |
| allow-notify rejects sender | Secondary receives NOTIFY but discards it as unauthorized | notify category log on secondary |
| notify-source port bug (GL #4002) | BIND 9.18.13 with explicit port in notify-source; one non-responsive secondary starves all others | Check BIND version and notify-source config |
| Secondary-to-secondary NOTIFY noise | Secondaries send NOTIFY to each other after every transfer | notify primary-only in options |
Quick checks
# Compare SOA serials (third field) between primary and secondary
dig @primary-ip example.com SOA +short | awk '{print $3}'
dig @secondary-ip example.com SOA +short | awk '{print $3}'
# Check zone state on the secondary (serial, expire, refresh)
rndc zonestatus example.com
# Inspect notify-related configuration (effective config, including defaults)
named-checkconf -p | grep -i "notify\|also-notify\|allow-notify"
# Check BIND version (relevant for GL #4002, fixed in 9.18.14)
rndc status | grep -i version
# Check notify log entries on primary (sent)
# Service name may be 'bind9' on Debian/Ubuntu
journalctl -u named --since "30 min ago" | grep -i notify
# Check notify log entries on secondary (received or rejected)
journalctl -u named --since "30 min ago" | grep -i notify
# Capture NOTIFY packets leaving the primary
# Trigger rndc reload example.com first, then watch for the packet
# timeout prevents hanging if no NOTIFY is sent
timeout 30 tcpdump -ni eth0 -c 5 udp port 53 and dst <secondary-ip>
# Capture NOTIFY packets arriving at the secondary
timeout 30 tcpdump -ni eth0 -c 5 udp port 53 and src <primary-ip>
<!-- TODO: verify exact JSON API path and default port for BIND statistics channel -->
# Check NOTIFY opcode counter on secondary (requires statistics-channel configured)
# Replace 8053 with your configured statistics-channel port
curl -s http://127.0.0.1:8053/json/v1/server | \
python3 -c "import sys,json; d=json.load(sys.stdin); print('NOTIFY:', d.get('opcodes',{}).get('NOTIFY','N/A'))"
How to diagnose it
Confirm the serial mismatch. Query SOA against both primary and secondary. If serials match, NOTIFY is working and the problem is elsewhere. If they differ, the secondary is behind.
Check the primary’s notify configuration. Run
named-checkconf -pand look for thenotifydirective. The default isyes, but it may be overridden toexplicitornoin the options block or per-zone. Ifnotify explicitis set, verifyalso-notifylists the secondary IPs. Withexplicit, BIND sends NOTIFY only to thealso-notifylist. An empty or missing list means zero NOTIFY messages.Verify NOTIFY packets leave the primary. Start
tcpdumpfiltering for UDP port 53 to the secondary’s IP, then triggerrndc reload example.com. If no packet appears, the primary is not sending NOTIFY. Check thenotifylog category for errors.Verify NOTIFY packets arrive at the secondary. Run
tcpdumpon the secondary filtering for UDP port 53 from the primary’s IP. If packets leave the primary but never arrive, the network path or firewall is dropping them. Stateful firewalls are a common culprit: NOTIFY is an unsolicited UDP packet, not a response to a query from the secondary, so return-traffic rules may not apply.Check the secondary’s acceptance log. If packets arrive but the secondary does not initiate a transfer, check the BIND
notifylog category on the secondary. If the NOTIFY was rejected, the sender IP was not in the NS RRset, theprimarieslist, or theallow-notifyACL. The log entry typically shows the rejection.Verify the zone transfer succeeds after NOTIFY. Even when NOTIFY is accepted, the secondary must reach the primary for a SOA query (UDP 53) and the zone transfer (TCP 53). Check
xfer-inlogs on the secondary for transfer success or failure. If the transfer fails, the secondary retries on the refresh schedule.Check for the GL #4002 bug. If you are running BIND 9.18.13 and
notify-sourceis configured with an explicit port, NOTIFY messages may be sent only to one non-responsive secondary while other secondaries are starved. Fix: remove the port specification fromnotify-sourceor upgrade to 9.18.14 or later.Force a transfer to confirm the path works. Run
rndc retransfer example.comon the secondary. This forces a full AXFR regardless of whether IXFR would normally be used, which can be heavy for large zones. If it succeeds, the TCP 53 path and TSIG are fine, and the problem is isolated to NOTIFY delivery. If it fails, focus on transfer-path issues first. Userndc refresh example.comfor a lighter check that does a normal serial comparison without forcing a full transfer.
Metrics and signals to monitor
| Signal | Why it matters | Warning sign |
|---|---|---|
| SOA serial consistency (primary vs secondary) | Direct measure of zone convergence | Secondary serial lower than primary for longer than the refresh interval |
| SOA expire runway | Countdown to zone removal on the secondary | Runway dropping below 50% of expire value |
| NOTIFY opcode counter (on secondary) | Counts NOTIFY messages received by this server | Counter not incrementing after known primary zone changes |
| Zone transfer counters | Transfer is the actual replication step | Failures increasing or successes flat while serial lags |
| NOTIFY log entries (notify category) | Shows send, receive, and reject events in real time | Missing “sending notify” on primary, or rejection messages on secondary |
| TCP connectivity on port 53 between primary and secondary | Transfers require TCP; NOTIFY alone does not update the zone | Transfer attempts fail or time out after NOTIFY is accepted |
Fixes
Firewall or network blocking NOTIFY
NOTIFY uses UDP port 53. Firewalls that allow TCP 53 for zone transfers but restrict UDP 53 between primary and secondary will silently drop NOTIFY. The secondary still converges on the SOA refresh timer, which masks the problem indefinitely.
Ensure UDP 53 is allowed bidirectionally between primary and secondary IPs. Stateful firewalls that track DNS connection state may need explicit allow rules because NOTIFY is unsolicited from the secondary’s perspective.
Wrong notify mode
The notify directive controls behavior:
yes(default): send to all NS records plusalso-notify.explicit: send only to thealso-notifylist. Ifalso-notifyis empty or missing, no NOTIFY is sent.primary-only: send NOTIFY only for zones where this server is the primary. Suppresses secondary-to-secondary NOTIFY.no: disable NOTIFY entirely.
If you find notify explicit without a matching also-notify, either add the secondary IPs to also-notify or change to notify yes.
Servers not in NS records
With notify yes, BIND sends NOTIFY only to servers in the zone’s NS RRset (plus also-notify). Any secondary, staging mirror, or hidden primary not listed in the NS records will never receive NOTIFY. Add them explicitly:
also-notify { <secondary-ip>; <secondary-ip2>; };
On the receiving side, if the NOTIFY source IP is not in the NS RRset or the secondary’s primaries list, add it to allow-notify:
allow-notify { <notify-source-ip>; };
allow-notify mismatch on the secondary
By default, a secondary accepts NOTIFY from senders in the zone’s NS RRset and from IPs in its primaries list. If the primary’s NOTIFY source IP does not match either (for example, NOTIFY arrives from a different interface, a load balancer, or a NAT address), the secondary silently discards it.
Add the primary’s NOTIFY source IP to allow-notify on the secondary. Use named-checkconf -p on the primary to confirm what notify-source is set to, then ensure that IP is authorized on the secondary.
notify-source port bug (GL #4002)
In BIND 9.18.13, if notify-source is configured with an explicit port and one secondary is non-responsive, BIND sends NOTIFY repeatedly to that secondary and never reaches the others. Fixed in 9.18.14.
Workaround: remove the port from notify-source. Specifying a port in notify-source, transfer-source, notify-source-v6, or the source arguments to also-notify and primaries has been deprecated since BIND 9.18.12 (GL #3781) and will be removed in a future release.
Secondary-to-secondary NOTIFY noise
By default, when a secondary loads a zone (including after a transfer), it sends NOTIFY to all NS records for that zone, including other secondaries. In most topologies this is harmless but generates unnecessary traffic and log noise. Suppress it with notify primary-only; in the options block, or notify no; in individual secondary zone definitions.
Hidden primary
If the primary is not listed in the zone’s NS records (a hidden primary), notify yes still sends NOTIFY to the secondaries that are in the NS RRset. The pitfall is on the receiving side: the secondary’s primaries list must include the hidden primary’s IP, or allow-notify must explicitly permit it, or the NOTIFY will be discarded as unauthorized. This is common in DDoS-protected authoritative setups where the real source of truth is not publicly reachable.
Prevention
Run named-checkconf before every rndc reload. A syntax error in one zone does not prevent named from accepting the reload command, but that zone silently fails to load.
After every zone change, confirm the secondary serial converges within seconds to a minute, not the full refresh interval. A serial comparison catches NOTIFY failures while they are still a delay and not an outage.
Track SOA expire runway proactively. Serial mismatch is the symptom; expire runway is the danger. A secondary serving stale data is a slow-motion failure that escalates to SERVFAIL when the expire timer fires.
Document the NOTIFY topology: which servers send NOTIFY to which, including also-notify entries and allow-notify ACLs. NOTIFY failures are frequently caused by topology changes (new secondary, IP change, primary moved) that were not reflected in the configuration.
How Netdata helps
- SOA serial consistency: comparing serials between primary and secondaries at per-second granularity catches a serial that fails to converge immediately after a known zone change, pointing at a NOTIFY or transfer problem.
- Zone transfer counters: flat or failing transfer counters correlated with a serial mismatch narrow the problem to the transfer path rather than NOTIFY delivery.
- NOTIFY opcode counter: the statistics channel exposes received NOTIFY messages as an opcode counter on the secondary. A counter that stops incrementing after primary zone changes points directly at a NOTIFY delivery problem.
- SOA expire runway trending: tracking the expire countdown over time reveals whether transfer failures are accumulating toward an outage, converting a propagation delay into an actionable alert before the zone expires.
- Correlation with network and firewall events: a serial mismatch that coincides with a firewall rule change or network partition confirms the root cause without manual
tcpdumpsessions.
Related guides
- How BIND actually works in production: a mental model for operators
- BIND monitoring checklist: the signals every production resolver and authoritative server needs
- BIND monitoring maturity model: from survival to expert
- named not responding on port 53: total outage versus UDP-works-TCP-fails
- BIND lame delegations: ’lame server resolving’ and nameservers that are not authoritative






