BIND as a Hidden Primary

Run BIND as a hidden primary behind your firewall and let an external secondary answer the queries.

What a hidden primary is

A hidden primary holds the master copy of your zone, but it does not appear in the zone's NS records. Resolvers never ask it. Only your secondaries talk to it: they pull the zone with AXFR or IXFR and answer every public query. You edit the zone on the primary, and the secondaries follow within seconds.

This setup gives you three things:

  • Your primary is not exposed to query floods or DDoS traffic. You can keep it on an internal network.
  • You keep full control of the zone file, your tooling, and your change process.
  • The public nameservers can be run by somebody else, on networks built for that load.

1. Declare the zone as primary

In named.conf, declare the zone with type primary. BIND versions before 9.16 use the older keyword type master, which means the same.

named.conf
zone "example.ch" {
  type primary;
  file "/etc/bind/zones/example.ch";
  // who may pull the zone
  allow-transfer {
    159.69.29.118;
    2a01:4f8:1c0c:63c4::118;
    45.131.252.44;
    2a11:8b80:1000:1::18;
  };
  // tell only these servers about changes
  notify explicit;
  also-notify {
    159.69.29.118;
    45.131.252.44;
  };
};

The addresses above are the ones hiddenprimary.ch uses. If you use a different secondary, put its transfer addresses there instead. For hiddenprimary.ch, allow all four addresses: two hosts can connect to your primary, the transfer node and the standby that takes over when the transfer node is unavailable. If you leave the standby out, your zone stops updating during that time.

notify explicit tells BIND to send NOTIFY messages only to the servers in also-notify. Without it, BIND also notifies every server named in the zone's NS records. That does no harm, but it is traffic you do not need.

2. Lock transfers down with TSIG (recommended)

An address list works, but a shared TSIG key is stronger: a request without the key is refused, whatever address it comes from. A key-based rule also covers every host of your secondary at once, so you do not have to update it when the secondary adds a node.

terminal
$ tsig-keygen -a hmac-sha256 example-transfer >> /etc/bind/named.conf.keys
named.conf
include "/etc/bind/named.conf.keys";
 
zone "example.ch" {
  type primary;
  file "/etc/bind/zones/example.ch";
  allow-transfer { key "example-transfer"; };
  notify explicit;
  also-notify { 159.69.29.118 key "example-transfer"; 45.131.252.44 key "example-transfer"; };
};

Give your secondary the same key name, algorithm, and secret. The TSIG guide shows how to enter the key on hiddenprimary.ch.

3. Open the firewall

Zone transfers need two things through your firewall:

  • Inbound TCP port 53 from the transfer addresses. AXFR and IXFR run over TCP.
  • Outbound UDP port 53 to the notify addresses, so your NOTIFY messages arrive.

The secondary also asks for your SOA record over UDP to see if the serial changed. Allow inbound UDP port 53 from the same addresses as well.

4. Check the configuration and reload

terminal
$ named-checkconf
$ named-checkzone example.ch /etc/bind/zones/example.ch
$ rndc reload

5. Change the zone the right way

A secondary only pulls a new copy when the SOA serial goes up. Every time you edit the zone file, increase the serial. A common format is YYYYMMDDNN, for example 2026092201 for the first change on 22 September 2026. Then reload the zone:

terminal
$ rndc reload example.ch

If the zone accepts dynamic updates (for example for ACME DNS-01 challenges), rndc reload does not read your edited file. BIND keeps the changes in a journal and ignores the file, but still reports success. Use rndc freeze example.ch, edit the file, then rndc thaw example.ch.

6. Verify the transfer

Compare what your secondary serves with your own file. Check the content, for example one record you just changed. The serial alone can mislead you: if the secondary signs the zone with DNSSEC, it uses its own serial.

terminal
$ dig +short TXT example.ch @ns1.hiddenprimary.ch

Common mistakes

  • The serial did not change. The secondary sees the same serial and keeps its old copy.
  • The firewall blocks TCP. The SOA check over UDP works, but every transfer fails.
  • The zone's NS records still name the old provider. Resolvers prefer the NS records inside your zone over the delegation at the registry. Update both. The migration guide explains the order.
  • The primary is listed in the NS records. Then it is not hidden: resolvers send it queries.

Next Steps