Install

# Debian / Ubuntu
sudo apt install knot

# Fedora / RHEL
sudo dnf install knot

# Verify
knotd --version
knotc --help

The minimum-viable config

Edit /etc/knot/knot.conf:

server:
    listen: [ 0.0.0.0@53, ::@53 ]
    user: knot:knot

log:
  - target: syslog
    any: info

template:
  - id: default
    storage: /var/lib/knot
    file: "%s.zone"          # zone file named after the zone (e.g. example.com.zone)
    semantic-checks: on
    dnssec-signing: on
    dnssec-policy: default

dnssec-policy:
  - id: default
    algorithm: ecdsap256sha256
    ksk-size: 256
    zsk-size: 256
    ksk-lifetime: 0          # never expire KSK (manually rotate)
    zsk-lifetime: 30d
    propagation-delay: 1h
    rrsig-lifetime: 14d
    rrsig-refresh: 7d
    nsec3: on
    nsec3-iterations: 0       # modern default; iterations is harmful
    nsec3-opt-out: off

zone:
  - domain: example.com
    template: default
    notify: secondaries
    acl: [ secondaries-xfr ]

  - domain: lab.example.com
    template: default

remote:
  - id: secondary-1
    address: 192.0.2.10@53

acl:
  - id: secondaries-xfr
    address: [ 192.0.2.10 ]
    action: transfer

  - id: localhost
    address: [ 127.0.0.1, ::1 ]
    action: [ notify, refresh, update ]

Create the zone file

Drop /var/lib/knot/example.com.zone:

$ORIGIN example.com.
$TTL 3600

@       IN  SOA  ns1.example.com. hostmaster.example.com. (
                    2026052201   ; serial (YYYYMMDDnn)
                    3600         ; refresh
                    1800         ; retry
                    604800       ; expire
                    300          ; minimum TTL
                )

@           IN  NS    ns1.example.com.
@           IN  NS    ns2.example.com.

ns1         IN  A     192.0.2.5
ns2         IN  A     192.0.2.6

@           IN  A     203.0.113.10
www         IN  A     203.0.113.10
mail        IN  A     203.0.113.20
@           IN  MX    10 mail
@           IN  TXT   "v=spf1 ip4:203.0.113.0/24 -all"

api         IN  A     203.0.113.30
api         IN  AAAA  2001:db8::30

Start it

sudo systemctl enable --now knot
sudo systemctl status knot

# Verify the zone is loaded + signed
sudo knotc zone-status example.com

# View the live zone (including DNSSEC RRSIGs)
sudo knotc zone-read example.com

# Verify with dig from another host
dig +dnssec @<your-server> example.com soa

If DNSSEC is configured (default in the template above), Knot auto-generates KSK + ZSK on first start, signs the zone, and uses NSEC3 for denial-of-existence proofs. Output of knotc zone-status shows the DNSKEY records.

Reload after zone changes

# Edit zone file, then bump the SOA serial (Knot won't reload otherwise)
sudo $EDITOR /var/lib/knot/example.com.zone

# Reload
sudo knotc zone-reload example.com

# Or for all zones
sudo knotc reload

DNSSEC: parent zone setup

Get the DS records to upload to the parent zone (your registrar):

sudo knotc zone-dnskey-set example.com
# Shows DNSKEY records (in the zone) and DS records (for the parent)

# Or use kdig to query directly
kdig @localhost example.com dnskey
kdig @localhost example.com ds

Submit the DS records to the registrar. After the parent publishes them, run an online DNSSEC verifier (e.g. dnsviz.net) to confirm the chain validates from the root.

Dynamic updates (DDNS)

For programmatic updates (cert-manager / cloudflared / scripted A-record updates):

acl:
  - id: ddns-update
    address: [ 127.0.0.1, 10.0.0.0/8 ]
    action: update

zone:
  - domain: dyn.example.com
    template: default
    acl: [ ddns-update ]

# Then use nsupdate to add records dynamically
nsupdate -k /etc/knot/key.conf
> server 127.0.0.1
> zone dyn.example.com
> update add host1.dyn.example.com 60 A 10.0.5.50
> send

Zone transfers to secondaries

For HA, run a second Knot (or other server) as secondary; primary auto-notifies on changes:

# On secondary
zone:
  - domain: example.com
    master: primary-1
    file: "%s.zone"
    serial-policy: increment

remote:
  - id: primary-1
    address: <primary-ip>@53

acl:
  - id: from-primary
    address: [ <primary-ip> ]
    action: [ notify ]

kdig: the better dig

Knot ships kdig as a replacement for dig with cleaner output + better DNSSEC visualization:

kdig @ns1.example.com example.com soa +dnssec
kdig -t any example.com
kdig -x 8.8.8.8        # reverse lookup
kdig +trace example.com   # walk the chain from the root

Drop-in for dig in most cases; output is more readable for DNSSEC-heavy zones.

Catalog zones (RFC 9432)

For "I have 200 small zones; managing each by hand is annoying":

# A catalog zone lists the member zones
zone:
  - domain: catalog.example.
    catalog-role: generate
    catalog-template: default
    file: "catalog.zone"

The catalog zone is itself a zone with PTR records for each member zone. Add a member by adding a PTR; Knot auto-creates the zone in memory + signs it. Useful for hosting providers with many customer zones.

Knot vs PowerDNS

  • Knot — primarily file-based; lean; fast; CZ.NIC pedigree. Less of an ecosystem for programmatic management.
  • PowerDNS (see that tutorial) — database-backed by default; REST API; richer ecosystem (PowerDNS-Admin UI, integrations).

Pick by workflow: for "I commit zone files to git + reload," Knot. For "I want a REST API to programmatically add records," PowerDNS.

Knot vs BIND

BIND is the elder; massive feature surface; correspondingly more knobs + larger attack surface. Knot is the modern, lean alternative; runs at ccTLD scale without BIND's operational quirks.

For recursion (not authoritative): Knot Resolver

Confusingly, Knot DNS (authoritative) and Knot Resolver (recursive) are different products. For "I want a recursive resolver for my LAN" (the role of Unbound / Pi-hole's backend), use Knot Resolver instead.

For "I want to be the authoritative server for my own domains," this tutorial.