# Configuration

To configure atomdns you need to write a
[Conffile](https://codeberg.org/miekg/dns/src/branch/main/cmd/atomdns/man/atomdns-conffile.5.md). In this
example we'll start with the most basic one and build it up from there.

> [!INFO]
> Each handler has a manual page too, use `man man/atomdns-conffile.5` to read a manual page. Or, if atomdns
> is installed on your system, just `man atomdns-conffile`.

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

```Caddyfile {linenos=true, title="The builtin configuration"}
{
    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](https://caddyserver.com/). 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:

```text {linenos=true, title="Startup logging"}
2026/03/05 15:31:46 INFO example.org. handlers.handler=log handlers.handler=whoami
2026/03/05 15:31:46 INFO Startup handler=global dns=[::]:1053 tcp=1024 run=1
2026/03/05 15:31:46 INFO Startup handler=global signal=HUP
2026/03/05 15:31:46 INFO Startup handler=log signal=USR1 enabled=true
2026/03/05 15:31:46 INFO Build GOOS=linux GOARCH=arm64 go=1.26.0 revision=e6bbbb0b92241cd992ed93a4c928ded6686e87cc
2026/03/05 15:31:46 INFO Listening total=1 roles.role=DNS:[::]:1053
2026/03/05 15:31:46 INFO Launched config=<builtin> PID=2088297 version=v072 dns=0.6.66 zones=1
  ┏━┓  ╺┳╸  ┏━┓  ┏┳┓
  ┣━┫   ┃   ┃ ┃  ┃┃┃  DNS
  ╹ ╹   ╹   ┗━┛  ╹ ╹ v072 (0.6.66)
  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. 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.
3. Another startup function from _global_: the HUP signal can be used to reload atomdns.
4. Here the {{< log >}} handler outputs that the
   USR1 signal is used. This allows for run-time toggling of the query logging.
5. Shows the build info for this binary.
6. Shows the revision.
7. Shows the roles of this server: the specific protocols and ports, here just DNS.
8. More information about the process, the config "file" loads, versions, and the process' PID.

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

```text
% 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

```text
2025/11/18 10:17:31 INFO example.org. network=udp remote=127.0.0.1 port=59444 id=40578 type=A class=IN name=whoami.example.org. 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

```text {lineos=true}
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](https://codeberg.org/miekg/dns/src/branch/main/cmd/atomdns/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.

dou
: defines a DNS over Unix sockets server. Does not require a `tls` section.

The `limits` section in each:

```Caddyfile
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](https://pkg.go.dev/runtime#NumCPU) to get that number. More details can be
found in the [global manual page](https://codeberg.org/miekg/dns/src/branch/main/cmd/atomdns/handlers/global#readme).

For the actual TLS setup and configuration see the [TLS Certificates](/running/tls) section, here we suffice
by saying we we manual certificates management.

Further more 3 zones are defined:

```Caddyfile
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.

```Caddyfile
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.

```Caddyfile
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](https://www.rfc-editor.org/rfc/rfc8976.html) record to be added.

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

```Caddyfile {id="atomdns.miek.nl" title="Conffile of atomdns.miek.nl"}
{
    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
    }
    dbfile zones/miek.nl.signed {
        import nlnetlabs
    }
}
```

The `import` directive is explained in the [Conffile manual](https://codeberg.org/miekg/dns/src/branch/main/cmd/atomdns/man/atomdns-conffile.5.md).
