Skip to main content
Several DNZ servers can share one Postgres or MySQL database and answer with the same zones and settings. A change made on any of them (dashboard, API, dnz CLI, DNS UPDATE, a Caddy or Traefik sync, an ACME challenge) reaches the others within a second. Clients reach them through plain NS records or one anycast address, and any of them serves the dashboard and the API. There is nothing to switch on: every DNZ server that uses the same database.url joins. SQLite stays one server; move to Postgres or MySQL first.
A cluster shares everything: zones, users, filtering, forwarding, proxies, settings and query logs. Replication is the other way to run more nameservers: each server keeps its own database and copies zones over AXFR/IXFR, and the secondaries may be any DNS server.

How it works

  • Change feed. Each server keeps zones, forwarding, blocklists and its other answer data in memory. Whenever one of them changes it, the server also writes a row to the database’s change feed, and the others load that part again. On Postgres they are woken at once with LISTEN/NOTIFY; on MySQL they poll four times a second (Postgres also polls every 2 seconds, in case its LISTEN connection drops). A node that was away for more than a day loads everything.
  • Leader. Jobs that write shared state run on one node at a time, the holder of a lease in the database. The leader renews it every 5 seconds; when it stops (crash, lost network), another node takes over after 15 to 20 seconds. A node that shuts down cleanly hands the lease over at once. Every new leader raises the lease’s generation, so a node that was cut off and comes back finds it has lost the lease.
  • Nodes. Each server has a stable node id (made on first start and kept in the data folder as node-id, or set with DNZ_NODE_ID) and reports its address, version and the last change it loaded every 5 seconds.

Set it up

You need a Postgres 13+ or MySQL 8.0.13+ database every node can reach, with its own backups (a managed database or a Postgres primary with a replica is a good fit: the cluster can’t run without it), the same DNZ version on every node, and clocks in sync (NTP). Each node keeps up to 16 connections open, 17 on Postgres.
1

Create the database

On the database server (Postgres):
Let the nodes connect: in pg_hba.conf, for nodes in 10.0.0.0/24:
For MySQL, create the user for the nodes’ addresses ('dnz'@'10.0.0.%'); see Database.
2

Start the first node

On the first machine (here 10.0.0.11). To bring an existing SQLite install along, run dnz db migrate-from-sqlite first.
Open the dashboard and create the first admin (First run). DNZ_CLUSTER_ADDRESS is what the Cluster page shows as the node’s address, and the host of its DNS address when DNS listens on 0.0.0.0.
3

Add more nodes

On each other machine, the same with its own name and address. On 10.0.0.12:
Settings → Cluster has these commands filled in under Add a node.
4

Check

Clients: NS records or anycast

For authoritative zones, give each node a name and list them all as the zone’s nameservers, at your registrar (with glue when the names are inside the zone) and in the zone itself:
Resolvers try another nameserver when one doesn’t answer, so a node that is down costs a retry, not an outage. Set DNZ_NAMESERVER=ns1.example.com so DNZ’s SOA records name it. For a resolver on a LAN (forwarding, filtering), hand out both addresses over DHCP (option domain-name-servers 10.0.0.11, 10.0.0.12;).
Every node serves the dashboard and the API, and sessions are in the database, so a load balancer in front of the nodes (or each node’s own HTTPS reverse proxy) needs no sticky sessions. The dnz CLI can talk to any node.

Watching it

  • Settings → Cluster (superadmin): each node’s address, DNS address, version, last heartbeat, whether it leads, and the change it has loaded against the feed’s last one. A node is down after 30 seconds without a heartbeat and behind when it hasn’t loaded a change that is 30 seconds old. Nodes gone for good can be removed from the list.
  • dnz cluster shows the same in the terminal.
  • Alerts: the leader sends one when a node goes down or falls behind, and again when it is back, to the notification providers subscribed to cluster_node.
  • The topology map shows the other nodes next to the DNZ box, colored by state.
  • GET /api/cluster returns it as JSON; GET /api/health on each node is the liveness check for load balancers and Docker.

Upgrading

Upgrade one node at a time. The first one on the new version upgrades the database’s tables when it starts; the others keep running meanwhile. A change from a newer node that an older one doesn’t know makes the older one load everything again, so mixed versions stay correct, but finish the upgrade soon after.

Good to know

  • Query logs and statistics are in the shared database: the Overview and the logs show the traffic of all nodes together.
  • GeoIP: each node keeps its own GeoIP database in its data folder; automatic updates download it on every node.
  • dnz-proxy: each dnz-proxy node streams its configuration from the DNZ node it connects to. ACME HTTP-01 challenges are handed out by the leader, which runs the issuance: point dnz-proxy nodes at the leader’s address, or use DNS-01 through DNZ’s own zones, which works from any node.
  • NOTIFY from a primary may arrive at any node; the leader transfers the zone within seconds.
  • Removing a node: stop it, then remove it under Settings → Cluster (or dnz cluster forget). Nodes not seen for 7 days are removed by themselves.
  • Containers without a data volume get a new node id on every start unless DNZ_NODE_ID is set.

Troubleshooting