Configuration

To configure atomdns you need to write a Conffile. In this example we’ll start with the most basic one and build it up from there.

When starting atomdns without any configuration it will use its builtin Conffile:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
        dns {
                addr [::]:1053
        }
}

example.org {
        log
        whoami
}

The “blocks” in this configuration are called “handler blocks”, you may recognize this format, as this is the format from Caddy. The order of the handlers is significant, here for example.org, it is: log, whoami. This means the request if first logged and then handed over to the whoami handler. If this was reversed, i.e. whoami, log, it would mean the request would not get logged, because whoami handles it, and does not call the next handler.

So just run it: ./atomdns, it will output something along these lines:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
INFO example.org. handlers=log,whoami
INFO Startup functions total=3
INFO Startup handler=global dns=[::]:1053 tcp=128 run=24
INFO Startup handler=global signal=HUP
INFO Startup handler=log signal=USR1
INFO Build GOOS=linux GOARCH=arm64 go=1.25.4 revision=56dc6813263dbfb0f64a4e3754f
INFO Launched config=<builtin> version=v042 dns=0.5.26 zones=1 roles=DNS:[::]:1053
  ┏━┓  ╺┳╸  ┏━┓  ┏┳┓
  ┣━┫   ┃   ┃ ┃  ┃┃┃  DNS
  ╹ ╹   ╹   ┗━┛  ╹ ╹ v042 (0.5.26)
  High performance and flexible DNS server
  https://atomdns.miek.nl
__________________________________\o/_______

The banner (line 8 and down) shows some information about atomdns. For the rest we have:

Lines:

  1. Show that we have example.org as the only zones, it has the handlers log and whoami configured and in that order.
  2. There are 3 startup functions defined, these functions show their logging in the next lines.
  3. This shows the configuration of the global handler (lines 1-5 in the figure at the start of this page). It shows the addresses and port atomdns runs on, here all adddresses and on port 1053.
  4. Another startup function from global: the HUP signal can be used to reload atomdns.
  5. Here the log handler outputs that the USR1 signal is used. This allows for run-time toggling of the query logging.
  6. Shows the build info for this binary.
  7. More information about the process, the config “file” loads, versions, number of zones configured and which roles the process has, in this case plain old DNS and again the addresses and ports.

The whoami handlers echos back your source port and IP address, so if we query this:

% dig +noall +answer +additional @localhost -p 1053 whoami.example.org

whoami.example.org.     0       IN      A       127.0.0.1
whoami.example.org.     0       IN      TXT     "Port: 59444 (udp)"

Meanwhile, because the log handler, logs we see

2025/11/18 10:17:31 INFO example.org. remote=127.0.0.1 port=59444 id=40578 type=A class=IN name=whoami.example.org. network=udp size=59 bufsize=1232 opcode=QUERY

Which, of course, shows the same information.

When you shut down atomdns by sending it the INT signal (i.e. ^C), it shows

2025/11/19 06:30:10 INFO Shutdown functions total=1
2025/11/19 06:30:10 INFO Shutdown handler=log signal=USR1
2025/11/19 06:30:10 INFO Received signal, stopping signal=interrupt

Which is showing the tear down function(s), in this case shows the log handler stopping with listening for the USR1 signal, and finally atomdns saying good bye.

More Complex Example

The Conffile-example that is included in the source is more complex and defines 3 zones, plus various additions in the global block See the global on what is defined here. In that configuration we define 3 roles for this server, namely:

dns
plain old DNS, this is done in the dns section, this listens on port 1053.
doh:
this defines DNS over HTTPS (and thus requires a tls section as well).
dot
defines a DNS over TLS server, which also requires a tls section.

The limits section in each:

limits {
    tcp -1
    run numcpu()*3
}

Tells after how many request over TCP the connection should be severed, in this case (-1) never. And run tells how many servers should be started and listen on the port, here: 3 times the number of CPUs in the system. This uses Go’s NumCPU to get that number. More details can be found in the global manual page.

For the actual TLS setup and configuration see the TLS Certificates section, here we suffice by saying we we manual certificates management.

Further more 3 zones are defined:

10.0.0.0/24 {
    log
    whoami
}

This is like the example above, except the zone is a reverse one, this is translated in a zone named 0.0.10.in-addr.arpa, the handlers are the same as above.

example.org {
    log
    dbfile dbfile/zone/testdata/db.example.org {
        transfer
    }
}

This handler block defines a handler chain for the example.org zone, it has log and dbfile the latter is used to serve zone data from file(s). Here we read from handlers/dbfile/zone/testdata/db.example.org. The “handlers” prefix comes from the root-directive in the global block - root handlers. The transfer property tells atomdns that zone transfers (AXFRs) are allowed.

miek.nl {
    log
    sign sign/testdata/db.miek.nl {
        key sign/testdata/Kmiek.nl.+013+59725
        zonemd
    }
    dbfile sign/testdata/db.miek.nl.signed
}

This is the most complex example in this file. We see dbfile , that serves db.miek.nl.signed. This zone is created via the sign that used the defined keyset to DNSSEC sign the input file: sign/testdata/db.miek.nl. With zonemd we tell that we also want the ZONEMD record to be added.

There are many handlers, so you can pick and choose what to run for your server, atomdns.miek.nl has the following configuration:

{
    log {
        debug
    }
    root /etc/atomdns
    health
    metrics
    dns {
        addr [::]:53
    }
}

(observe) {
    metrics
}

(nlnetlabs) {
    transfer {
            to 185.49.140.62 2a04:b900::8:0:0:62 {
            source 2a10:3781:2dc2:3::53
        }
    }
}

miek.nl {
    import observe
    sign zones/miek.nl {
        key keys/Kmiek.nl.+008+33694 keys/Kmiek.nl.+013+05607
        directory zones
    }
    dbfile zones/miek.nl.signed {
        import nlnetlabs
    }
}

The import directive is explained in the Conffile manual.