2. Slave operation

2.1. Supermaster automatic provisioning of slaves
2.2. Modifying a slave zone using a script

On launch, PDNS requests from all backends a list of domains which have not been checked recently for changes. This should happen every 'refresh' seconds, as specified in the SOA record. All domains that are unfresh are then checked for changes over at their master. If the SOA serial number there is higher, the domain is retrieved and inserted into the database. In any case, after the check the domain is declared 'fresh', and will only be checked again after 'refresh' seconds have passed.


Slave support is OFF by default, turn it on by adding slave to the configuration. The same holds for master operation. Both can be on simultaneously.

PDNS also reacts to notifies by immediately checking if the zone has updated and if so, retransfering it.

All backends which implement this feature must make sure that they can handle transactions so as to not leave the zone in a half updated state. MySQL configured with either BerkeleyDB or InnoDB meets this requirement, as do PostgreSQL and Oracle. The Bindbackend implements transaction semantics by renaming files if and only if they have been retrieved completely and parsed correctly.

Slave operation can also be programmed using several pdns_control commands, see Section 1.1, “pdns_control”. The 'retrieve' command is especially useful as it triggers an immediate retrieval of the zone from the configured master.

Since version 2.9.21, PowerDNS supports multiple masters. For the BIND backend, the native BIND configuration language suffices to specify multiple masters, for SQL based backends, list all master servers separated by commas in the 'master' field of the domains table.

2.1. Supermaster automatic provisioning of slaves

PDNS can recognize so called 'supermasters'. A supermaster is a host which is master for domains and for which we are to be a slave. When a master (re)loads a domain, it sends out a notification to its slaves. Normally, such a notification is only accepted if PDNS already knows that it is a slave for a domain.

However, a notification from a supermaster carries more persuasion. When PDNS determines that a notification comes from a supermaster and it is bonafide, PDNS can provision the domain automatically, and configure itself as a slave for that zone.

Before a supermaster notification succeeds, the following conditions must be met:

  • The supermaster must carry a SOA record for the notified domain

  • The supermaster IP must be present in the 'supermaster' table

  • The set of NS records for the domain, as retrieved by the slave from the supermaster, must include the name that goes with the IP address in the supermaster table


If you use another PowerDNS server as master and have DNSSEC enabled on that server please don't forget to rectify the domains after every change. If you don't do this there is no SOA record available and one requirement will fail.

So, to benefit from this feature, a backend needs to know about the IP address of the supermaster, and how PDNS will be listed in the set of NS records remotely, and the 'account' name of your supermaster. There is no need to fill the account name out but it does help keep track of where a domain comes from.

2.2. Modifying a slave zone using a script

As of version 3.0, the PowerDNS Authoritative Server can invoke a Lua script on an incoming AXFR zone transfer. The user-defined function axfrfilter within your script is invoked for each resource record read during the transfer, and the outcome of the function defines what PowerDNS does with the records.

(idea and documentation contributed by Jan-Piet Mens)

What you can accomplish using a Lua script:

  • Ensure consistent values on SOA

  • Change incoming SOA serial number to a YYYYMMDDnn format

  • Ensure consistent NS RRset

  • Timestamp the zone transfer with a TXT record

To enable a Lua script for a particular slave zone, determine the domain_id for the zone from the `domains` table, and add a row to the `domainmetadata` table for the domain. Supposing the domain we want has an `id` of 3, the following SQL statement will enable the Lua script `my.lua` for that domain:

	INSERT INTO domainmetadata (domain_id, kind, content) VALUES (3, "LUA-AXFR-SCRIPT", "/lua/my.lua");

The Lua script must both exist and be syntactically correct; if not, the zone transfer is not performed.

Your Lua functions have access to the query codes through a pre-defined Lua table called `pdns`. For example if you want to check for a CNAME record you can either compare `qtype` to the numeric constant 5 or the value `pdns.CNAME` -- they are equivalent.

If your function decides to handle a resource record it must return a result code of 0 together with a Lua table containing one or more replacement records to be stored in the back-end database. If, on the other hand, your function decides not to modify a record, it must return pdns.PASS and an empty table indicating that PowerDNS should handle the incoming record as normal. If your function decides to drop a query and not respond whatsoever, it must return pdns.DROP and an empty table indicating that the recursor does not want to process the packet in Lua nor in the core recursor logic.

Consider the following simple example:

	function axfrfilter(remoteip, zone, qname, qtype, ttl, prio, content)

	   -- Replace each HINFO records with this TXT
	   if qtype == pdns.HINFO then
	      resp = {}
	      resp[1] = {    qname   = qname,
		    qtype   = pdns.TXT,
		    ttl   = 99,
		    content   = "Hello Ahu!"
	      return 0, resp

	   -- Grab each _tstamp TXT record and add a time stamp
	   if qtype == pdns.TXT and string.starts(qname, "_tstamp.") then
	      resp = {}
	      resp[1] = {
		    qname   = qname,
		    qtype   = qtype,
		    ttl   = ttl,
		    content   = os.date("Ver %Y%m%d-%H:%M")
	      return 0, resp

	   resp = {}
	   return pdns.PASS, resp

	function string.starts(s, start)
	   return s.sub(s, 1, s.len(start)) == start

Upon an incoming AXFR, PowerDNS calls our `axfrfilter` function for each record. All HINFO records are replaced by a TXT record with a TTL of 99 seconds and the specified string. TXT Records with names starting with `_tstamp.` get their value (_rdata_) set to the current time stamp. All other records are unhandled.