DNSSEC

Posted on 7 2026

DNSSEC is one of those topics that generates a lot of hand-waving about cryptographic signing and chain of trust without anyone explaining what problem it is actually solving or what it costs to enable it. This article covers both, and then walks through enabling it on February’s internal zones.

What DNSSEC does

DNS responses are unauthenticated by default. When you ask a resolver for the IP address of a hostname, there is no mechanism in the original DNS protocol to verify that the answer you receive is the same one the authoritative server sent. A response can be forged or modified in transit, and neither the client nor the resolver has any way to detect it. This is the DNS cache poisoning attack surface: feed a resolver a false answer, it caches it, and everyone who asks that resolver gets the false answer until the TTL expires.

DNSSEC addresses this by adding cryptographic signatures to DNS records. The authoritative server signs its records with a private key. Resolvers that support DNSSEC validation check those signatures against a published public key. If the signature does not verify, the resolver refuses to return the answer. A forged or tampered response will fail validation and be discarded.

The chain of trust works hierarchically. The root DNS zone is signed, and its keys are publicly known. Each signed child zone publishes a DS record in its parent zone that chains back up to the root. A validating resolver can walk this chain from the root down to verify any signed zone.

Why it matters for internal zones

You might reasonably wonder whether signing internal zones is worth the effort. The chain of trust for an internal zone cannot extend to the public root, because the public root has no knowledge of home.arpa or any other private zone. The whole point of DNSSEC for public zones is that chain; without it, what does signing an internal zone actually achieve?

The answer is: it gives the Recursor a way to validate responses from the Authoritative Server, even for zones that are not anchored to the public root. You configure a trust anchor directly in the Recursor that tells it to trust the Authoritative Server’s key for specific zones. From that point on, the Recursor validates responses from the Authoritative Server cryptographically rather than taking them on faith. If someone manages to intercept traffic between the two processes (unlikely on localhost, but not impossible in more complex setups), a tampered response will fail validation.

The more practical reason to enable DNSSEC on internal zones now is that it avoids a problem. The Recursor performs DNSSEC validation by default. When it forwards queries for internal zones to the Authoritative Server, it expects either signed responses or explicit confirmation that the zone is unsigned. Without one of those two things, the Recursor may treat responses as bogus and refuse to return them. Signing the zones is the clean solution. The alternative is adding negative trust anchors telling the Recursor to skip validation for specific zones, which works but is a deliberate step backwards.

Enabling DNSSEC on the Authoritative Server

The gmysql-dnssec=yes line in the backend config from the previous article already told the Authoritative Server to store DNSSEC key material in MariaDB. The database schema includes the necessary tables. All that is left is generating keys and signing each zone.

For each internal zone, run:

sudo pdnsutil zone secure home.arpa
sudo pdnsutil zone rectify home.arpa

zone secure generates a Combined Signing Key (CSK) using ECDSA P-256 with SHA-256, which is algorithm 13 in DNSSEC terms. This is the current PowerDNS default and a good choice: fast, secure, and well-supported by modern validators. The key is stored in MariaDB alongside the zone data.

zone rectify adds the NSEC records that DNSSEC needs to prove the non-existence of names. Run it after signing and after any subsequent changes to the zone.

Repeat for each internal zone:

sudo pdnsutil zone secure february.local
sudo pdnsutil zone rectify february.local

Confirm the zones are signed:

sudo pdnsutil zone check home.arpa
sudo pdnsutil list-keys home.arpa

zone check will report any problems with the zone’s DNSSEC setup. list-keys shows the active key and its ID, which you will need in a moment.

Extracting the trust anchor

For the Recursor to validate responses from the Authoritative Server, it needs to know the zone’s public key. Export the DS record for each signed zone:

sudo pdnsutil export-zone-ds home.arpa
sudo pdnsutil export-zone-ds february.local

The output looks something like this:

home.arpa. IN DS 12345 13 2 4a3b...

The numbers are the key tag, algorithm, digest type, and digest. Keep the full output for each zone; you need it in the next step.

Configuring the Recursor to validate

The Recursor needs two things: DNSSEC validation enabled, and a trust anchor for each internal zone.

Edit /etc/powerdns/recursor.conf and add or confirm the DNSSEC validation setting:

dnssec=validate

This tells the Recursor to validate all DNSSEC-signed responses. Unsigned zones from the internet are treated as insecure but still returned; only responses that are signed but fail validation are rejected as bogus.

For the trust anchors, create a Lua config file at /etc/powerdns/recursor.lua:

-- Trust anchors for internal zones
addTA("home.arpa", "12345 13 2 4a3b...")
addTA("february.local", "67890 13 2 9f1c...")

Replace the DS record content with the actual output from export-zone-ds. The format is everything after the zone name and IN DS on the output line.

Tell the Recursor to load the Lua config:

lua-config-file=/etc/powerdns/recursor.lua

Restart the Recursor:

sudo systemctl restart pdns-recursor

Testing validation

Query an internal zone and check whether the response is marked as validated. The ad flag in the dig output means Authentic Data, which indicates the Recursor validated the response successfully:

dig +dnssec february.home.arpa @127.0.0.1

In the flags section of the response, look for ad:

;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

If ad is present, validation is working. If the response is SERVFAIL instead, the trust anchor is wrong or the zone signing has a problem. Check pdnsutil zone check and confirm the DS content in the Lua config matches the export-zone-ds output exactly.

To confirm the Recursor is rejecting tampered responses, you can test against the Authoritative Server directly (bypassing the Recursor) and compare. The Authoritative Server will return RRSIG records alongside the answer. If you manually corrupt the query and it still resolves through the Recursor, something is misconfigured.

Key rotation

DNSSEC keys should be rotated periodically. For internal zones with a locally-configured trust anchor this is straightforward but requires updating both the Authoritative Server and the Recursor config in sequence.

When you are ready to rotate:

sudo pdnsutil zone add-zone-key home.arpa csk active

This generates a new key alongside the existing one. Export the new DS record, update recursor.lua to include both the old and new DS records, restart the Recursor, then after a suitable rollover period remove the old key from the Authoritative Server and the old trust anchor from the Recursor config.

For a homelab with a locally-managed trust anchor, the rollover period can be short: a few minutes is enough since there are no upstream DS records to propagate through the public DNS hierarchy.

The alternative: negative trust anchors

If you decide signing internal zones is more complexity than you want to take on right now, the other option is to tell the Recursor explicitly not to validate specific zones. In recursor.lua:

addNTA("home.arpa", "internal unsigned zone")
addNTA("february.local", "internal unsigned zone")

This suppresses validation for those zones entirely. Responses from the Authoritative Server are accepted without checking signatures. It is a pragmatic choice for a homelab; just be aware you are opting out of the protection DNSSEC provides rather than enabling it.

The signed approach is worth doing if you are willing to maintain it. The negative trust anchor approach is honest about what it is.