Zone Transfers

Zone Transfers (AXFR / IXFR)

Nexus GSLB implements RFC 5936 full zone transfer (AXFR) and RFC 1995 incremental zone transfer (IXFR, served as AXFR fallback). This allows secondary nameservers, CDN providers, and monitoring tools to pull a consistent snapshot of the zone.


Overview

Property Value
Protocol DNS QUERY, qtype AXFR (252) or IXFR (251)
Standards RFC 5936 (AXFR), RFC 1995 (IXFR)
Transport TCP/53 only — UDP AXFR requests receive REFUSED
TSIG Optional by default; can be required via requireTsig
IXFR handling Full AXFR is returned — Nexus does not track per-serial deltas
Default state Disabled — must opt in via dns.axfr.enabled: true

Configuration

dns:
  domain: "gslb.cc"
  nsNames:
    - "ns1.gslb.cc"
    - "ns2.gslb.cc"
  axfr:
    enabled: true       # required — AXFR is disabled by default
    requireTsig: true   # recommended for public-facing zones

Fields

Field Type Default Description
enabled bool false Allow AXFR/IXFR responses. Must be true to serve any transfers.
requireTsig bool false Reject unsigned transfer requests with REFUSED. Recommended for public authoritative zones. When false, unsigned requests are accepted alongside TSIG-authenticated ones.

Zone contents

Each transfer response contains the following record sets, in order:

Records Source
SOA (opening) Synthesised from dns.nsHostname, dns.adminEmail, serial = current Unix timestamp
NS All hostnames in dns.nsNames, TTL 3600
A / AAAA All pool members with enabled: true, using each service's configured TTL. Disabled members are excluded. Health state is not considered — all enabled members appear regardless of current health.
TXT All TXT records managed via the API or RFC 2136 dynamic updates
SOA (closing) Identical to the opening SOA — required bookend per RFC 5936 §2.2

GSLB note on A/AAAA records

In a standard authoritative zone, a domain name resolves to a fixed set of IPs. In a GSLB zone, the DNS server returns a single IP selected by the load-balancing algorithm on each query. Zone transfers expose the full member set rather than a single selected answer, because secondary servers have no health state.

This is intentional — secondaries should have all IPs available so that if the primary becomes unreachable, the secondary serves some answer rather than NXDOMAIN. Combine secondary serving with short TTLs (10–60s) to minimise stale-cache effects when members fail.


TSIG authentication

If the client request includes a TSIG record, Nexus validates it against the TSIG key store (the same store used for RFC 2136 dynamic updates). If validation fails, the server responds with NOTAUTH.

If the client request has no TSIG and requireTsig: true is set, the server responds with REFUSED.

To create a TSIG key for a secondary DNS server:

curl -X POST https://nexus-api.example.com/api/v1/dns/tsig-keys \
  -H "Authorization: Bearer $GSLB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "secondary-bind9.gslb.cc.", "algorithm": "hmac-sha256."}'

Save the returned secret — it is shown once and not retrievable later. See RFC 2136 reference for full TSIG key management API.


Message framing

Records are streamed in multiple TCP messages rather than one large frame. Each message carries at most 100 RRs, keeping frames well under 16 KB. The SOA bookend is always in the final message. All messages in a single transfer share the same SOA serial, so receivers see a consistent snapshot even if DB state changes mid-transfer.


IXFR fallback

Nexus does not maintain per-serial zone diffs. When a secondary requests IXFR, Nexus responds with a full AXFR transfer. This is valid per RFC 1995 §4:

"If an IXFR query with the same or newer serial number is received, it MUST be treated as an AXFR query."

All major secondary DNS implementations (BIND 9, Knot DNS, NSD, CoreDNS, PowerDNS) accept AXFR in response to IXFR and handle it transparently.


Permissions

Zone transfers are served by the DNS server directly and do not go through the REST API permission layer. Access control is via:

  1. dns.axfr.enabled — global on/off switch
  2. dns.axfr.requireTsig — require a valid TSIG signature
  3. Firewall rules on port 53/TCP — restrict by source IP at the network level

There is no per-tenant access control for AXFR. Any client with a valid TSIG key (from any tenant) can transfer the zone when requireTsig: true. Use firewall rules when tighter control is needed.


Troubleshooting

REFUSED on AXFR request

  • dns.axfr.enabled is false — set it to true and restart.
  • The request came over UDP — AXFR requires TCP. Verify your secondary is configured for TCP transfer.
  • requireTsig: true is set but the request has no TSIG key — add a TSIG key to the secondary configuration.

NOTAUTH on AXFR request

  • TSIG signature validation failed. Common causes:
    • Wrong key name (must match exactly, including trailing dot)
    • Wrong secret value
    • Clock skew greater than 5 minutes between secondary and primary

Empty zone (SOA only, no A records)

  • No services are configured, or all configured services have no pool assigned (pool_id IS NULL)
  • All pool members are disabled (enabled: false)
  • Database is nil (DNS server started without a DB path)

Test the transfer manually

# Unsigned
dig AXFR gslb.cc @<nexus-ip> -p 53

# With TSIG (requires bind-utils / dig with TSIG support)
dig AXFR gslb.cc @<nexus-ip> -p 53 \
  -y "hmac-sha256:secondary-bind9.gslb.cc.:<base64-secret>"

Code references

  • internal/dns/axfr.go: handleAXFR, buildZoneRRs
  • internal/dns/server.go: AXFRConfig, routing in handleDNSRequest
  • internal/storage (rqlite.go + storage_*.go): ListServices, ListAllTXTRecords

Was this article helpful?
© 2026