Applies to: BIND 9.20.29 (Debian 13), BIND 9.18 (Ubuntu 24.04, AlmaLinux 10), dig 9.18 and 9.20; Windows Server DNS according to Microsoft
A DNS zone transfer is how a secondary name server gets a full copy of a domain's DNS records from the primary server. The full copy is called AXFR; an incremental update that sends only the changes is IXFR. Transfers are meant only for your own secondary servers. If any IP on the internet can request one, anyone can download every record in the zone, so restrict transfers with allow-transfer in BIND (or the Zone Transfers setting in Windows DNS) to your secondary servers' IPs or a TSIG key.
How a zone transfer works
- The primary (master) server holds the editable zone file. One or more secondary (slave) servers answer queries with a copy of it. Both are listed as NS records for the domain, so if one is down, the other still answers.
- Each zone has an SOA record with a serial number. When you change a record, you raise the serial.
- The primary sends a NOTIFY message to the secondaries (RFC 1996). Without it, each secondary checks the primary's serial every refresh interval from the SOA record.
- If the primary's serial is higher, the secondary asks for an IXFR (RFC 1995) or a full AXFR (RFC 5936). Transfers run over TCP port 53.
- If a secondary cannot reach the primary for longer than the SOA expire time, it stops answering for the zone.
This is the test zone we used in the lab. The SOA values in brackets are serial, refresh, retry, expire and negative TTL, in seconds:
$TTL 3600
@ IN SOA ns1.example.com. hostmaster.example.com. (
2026101001 ; serial
3600 ; refresh
900 ; retry
1209600 ; expire
300 ) ; negative TTL
IN NS ns1.example.com.
IN NS ns2.example.com.
IN A 192.0.2.10
ns1 IN A 192.0.2.53
ns2 IN A 198.51.100.53
www IN A 192.0.2.10
mail IN A 192.0.2.25
IN MX 10 mail.example.com.
vpn IN A 192.0.2.99
Why an open zone transfer is a problem
Normal DNS lookups only answer names you already know. An open AXFR hands over the whole list, including names you never published anywhere, such as vpn, staging, backup or dev, and the IP addresses behind them. That map of your network makes scanning and targeted attacks easier. It does not let anyone change your records, but there is no reason to give it away.
How to test a domain for open zone transfers
- Find the domain's name servers:
dig +short NS example.com
- Ask each name server for a full transfer:
dig axfr example.com @ns1.example.com
- Check every name server, not just the first: an old secondary that nobody updated can still be open.
A secured server refuses. This is what we got from one of the name servers of iana.org (a well-known domain, used here only as an example):
$ dig axfr iana.org @c.iana-servers.net. ; <<>> DiG 9.18.39-0ubuntu0.24.04.7-Ubuntu <<>> axfr iana.org @c.iana-servers.net. ;; global options: +cmd ; Transfer failed. $ host -l iana.org c.iana-servers.net. ... Host iana.org not found: 5(REFUSED) ; Transfer failed.
An open server sends the records. ICANN offers zone transfers on purpose from xfr.lax.dns.icann.org (RFC 8806 lists it as a source of the root zone). Here is a successful AXFR of the small root-servers.net zone from that server (shortened):
$ dig axfr root-servers.net @xfr.lax.dns.icann.org root-servers.net. 3600000 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2026100700 14400 7200 1209600 3600000 root-servers.net. 3600000 IN NS a.root-servers.net. root-servers.net. 3600000 IN NS b.root-servers.net. ... root-servers.net. 3600000 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2026100700 14400 7200 1209600 3600000 ;; SERVER: 192.0.32.132#53(xfr.lax.dns.icann.org) (TCP) ;; XFR size: 42 records (messages 1, bytes 956)
If your own domain answers like the second example, fix it with the steps below. If your DNS is hosted by your registrar, Cloudflare or your hosting control panel, you do not run zone transfers yourself; this matters when you run your own BIND or Windows DNS server.
Check your BIND version first: the default changed
BIND 9.20.0 changed the default. According to the BIND release notes, outgoing zone transfers are no longer enabled by default, and you must set allow-transfer to allow them. We printed the built-in defaults with named -C:
- BIND 9.20.29 (Debian 13):
allow-transfer {none;}; - BIND 9.18.39 (Ubuntu 24.04) and 9.18.33 (AlmaLinux 10):
allow-transfer {any;};
Neither package sets allow-transfer in its stock config. Ubuntu 24.04's named.conf.options also has listen-on-v6 { any; };, so a public zone added there without allow-transfer can be transferred by anyone. AlmaLinux 10's stock /etc/named.conf listens on 127.0.0.1 only and allows queries from localhost only, but that changes as soon as you open it up for a public zone. Check with named -v and set allow-transfer explicitly on every version.
How to secure zone transfers in BIND
- Deny transfers by default in the
optionsblock ofnamed.conf(on Debian and Ubuntu,/etc/bind/named.conf.options):options { ... allow-transfer { none; }; }; - Create a TSIG key so the primary can tell your secondary apart from anyone using its IP:
$ tsig-keygen -a hmac-sha256 xfr-key key "xfr-key" { algorithm hmac-sha256; secret "REDACTED"; };Save the output to a file such as/etc/bind/xfr-key.conf, keep it private, and copy the same file to the secondary over SSH. Never paste the secret anywhere public. - Allow the secondary in the zone on the primary. The zone setting overrides the one in
options:include "/etc/bind/xfr-key.conf"; zone "example.com" { type primary; file "/etc/bind/db.example.com"; allow-transfer { key "xfr-key"; }; also-notify { 198.51.100.53; }; notify yes; };To allow by IP instead, or as well, writeallow-transfer { 198.51.100.53; key "xfr-key"; };. Both forms passednamed-checkconfon BIND 9.18 and 9.20. - Use the key on the secondary:
include "/etc/bind/xfr-key.conf"; server 192.0.2.53 { keys { "xfr-key"; }; }; zone "example.com" { type secondary; primaries { 192.0.2.53 key "xfr-key"; }; file "/var/cache/bind/db.example.com"; };The older keywordstype slaveandmastersare still accepted by BIND 9.18 and 9.20. - Check and reload on each server:
named-checkconf -z(it printszone example.com/IN: loaded serial 2026101001when the zone file is valid), thenrndc reload. - Test from another machine:
dig axfr example.com @ns1.example.commust show; Transfer failed.. From the secondary,dig axfr example.com @192.0.2.53 -k /etc/bind/xfr-key.confsigns the request with the key and should return the zone.
Two practical notes. First, do not try to stop transfers by blocking TCP port 53: normal DNS answers also use TCP, and RFC 7766 requires DNS servers to support it. Second, if a secondary does not pick up a change, the usual cause is a serial number that was not raised. After fixing it, run rndc notify example.com on the primary or rndc retransfer example.com on the secondary.
Windows Server DNS
According to Microsoft, you set this in DNS Manager under the zone's Properties > Zone Transfers tab: keep Allow zone transfers off, or choose Only to the following servers and add your secondary's IP. In PowerShell the -SecureSecondaries values are NoTransfer, TransferAnyServer, TransferToZoneNameServer and TransferToSecureServers (only the servers you list). For example:
Set-DnsServerPrimaryZone -Name "example.com" -SecureSecondaries TransferToSecureServers -SecondaryServers 198.51.100.53
Never choose "To any server" (TransferAnyServer) for a public zone.
Running your own primary and secondary name servers works well on two small servers, such as two KVM VPS plans with full root access.
Related articles: understanding DNS zone files, the named.conf file explained, how to use the rndc command, DNS lookup commands: dig, nslookup and host, how to set up a DNS server on Windows Server.
Official documentation: BIND 9 configuration reference, RFC 5936 (AXFR), Set-DnsServerPrimaryZone.