# 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: {{}}, {{}}. This means the request if first logged and then handed over to the {{}} handler. If this was reversed, i.e. {{}}, {{}}, it would mean the request would not get logged, because {{}} 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= 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).