Introduction to DNSSEC
DNSSEC (Domain Name System Security Extensions) adds a layer of cryptographic authentication to DNS responses. Without DNSSEC, an attacker can forge DNS replies (cache poisoning, man-in-the-middle) and redirect users to malicious hosts. DNSSEC solves this by digitally signing every DNS record set in a zone so that resolvers can verify the authenticity and integrity of the data they receive.
This guide walks you through the entire process of enabling DNSSEC on an authoritative BIND9 server running on Ubuntu 22.04 or 24.04. We will cover key generation, zone signing, DS record submission, DNSSEC validation on resolvers, key rollover strategy, and automated re-signing with
inline-signing.
Prerequisites
- Ubuntu 22.04 LTS or 24.04 LTS server with root or sudo access
- BIND9 installed and serving at least one authoritative zone (see our guide: How to Install BIND9 on Ubuntu)
- Access to your domain registrar's control panel (to submit DS records)
- NTP synchronised clock (
timedatectl status
should show System clock synchronized: yes)
Step 1 — Verify Your Current BIND9 Installation
Before configuring DNSSEC, confirm BIND is running and your zone is resolving properly.
sudo systemctl status named
dig @localhost example.com SOA +short
You should see an active service and a valid SOA record. Also verify the BIND version supports DNSSEC features:
named -V | grep -i dnssec
Look for
--with-dnssecor DNSSEC-related libraries in the output. All modern Ubuntu packages of BIND9 (9.18+) include full DNSSEC support.
Step 2 — Understand DNSSEC Key Types
DNSSEC uses two types of keys:
- ZSK (Zone Signing Key): Signs the individual record sets (RRsets) in your zone. Rotated more frequently (every 1–3 months).
- KSK (Key Signing Key): Signs the DNSKEY RRset itself. Its hash is published as a DS record in the parent zone. Rotated less frequently (every 1–2 years).
We will use the ECDSAP256SHA256 algorithm (algorithm 13), which is the current best practice recommended by NIST and the IETF (RFC 8624). It provides strong security with small key and signature sizes.
Step 3 — Create a Key Directory
sudo mkdir -p /etc/bind/keys/example.com
sudo chown bind:bind /etc/bind/keys/example.com
sudo chmod 750 /etc/bind/keys/example.com
Keeping keys in a dedicated directory with restricted permissions is a security best practice.
Step 4 — Generate the KSK and ZSK
4a — Generate the KSK
cd /etc/bind/keys/example.com
sudo dnssec-keygen -a ECDSAP256SHA256 -n ZONE -f KSK example.com
This produces two files, for example:
Kexample.com.+013+12345.key (public)
Kexample.com.+013+12345.private (private)
4b — Generate the ZSK
sudo dnssec-keygen -a ECDSAP256SHA256 -n ZONE example.com
This produces another key pair, for example:
Kexample.com.+013+67890.key
Kexample.com.+013+67890.private
4c — Set correct ownership
sudo chown bind:bind /etc/bind/keys/example.com/*
sudo chmod 640 /etc/bind/keys/example.com/*.private
sudo chmod 644 /etc/bind/keys/example.com/*.key
Step 5 — Include the Keys in Your Zone File
Open your zone file (e.g.,
/etc/bind/zones/db.example.com) and add
$INCLUDEdirectives at the bottom:
; /etc/bind/zones/db.example.com
$TTL 86400
@ IN SOA ns1.example.com. admin.example.com. (
2026021501 ; Serial
3600 ; Refresh
1800 ; Retry
1209600 ; Expire
86400 ) ; Negative Cache TTL
@ IN NS ns1.example.com.
@ IN NS ns2.example.com.
ns1 IN A 203.0.113.10
ns2 IN A 203.0.113.11
@ IN A 203.0.113.50
www IN CNAME @
mail IN A 203.0.113.60
@ IN MX 10 mail.example.com.
$INCLUDE /etc/bind/keys/example.com/Kexample.com.+013+12345.key
$INCLUDE /etc/bind/keys/example.com/Kexample.com.+013+67890.key
Important: Replace the key file names with the actual file names generated in Step 4. Increment the serial number every time you edit the zone.
Step 6 — Sign the Zone (Manual Method)
For learning and one-time setups, you can sign the zone manually:
cd /etc/bind/zones
sudo dnssec-signzone \
-A \
-3 $(head -c 1000 /dev/urandom | sha1sum | cut -b 1-16) \
-N INCREMENT \
-o example.com \
-t \
-K /etc/bind/keys/example.com \
db.example.com
Flags explained:
-A
— generate NSEC3 records for all sets-3 <salt>
— use NSEC3 with a random salt (mitigates zone enumeration)-N INCREMENT
— automatically increment the SOA serial-o example.com
— origin zone name-t
— print signing statistics-K
— directory containing the keys
The command produces
db.example.com.signedand a
dsset-example.com.file containing the DS records.
Step 7 — Update named.conf to Use the Signed Zone
Edit your zone declaration in
/etc/bind/named.conf.local:
zone "example.com" {
type master;
file "/etc/bind/zones/db.example.com.signed";
allow-transfer { 203.0.113.11; };
};
Validate the configuration and reload:
sudo named-checkconf
sudo named-checkzone example.com /etc/bind/zones/db.example.com.signed
sudo rndc reload example.com
Step 8 — Configure Automatic Inline Signing (Recommended for Production)
Manual signing is error-prone. BIND9 supports inline-signing, which automatically signs the zone and manages re-signing before signatures expire.
zone "example.com" {
type master;
file "/etc/bind/zones/db.example.com";
key-directory "/etc/bind/keys/example.com";
inline-signing yes;
auto-dnssec maintain;
allow-transfer { 203.0.113.11; };
};
Note on BIND 9.18+: The
auto-dnssecdirective is deprecated in favour of
dnssec-policy. Here is the modern approach:
// In /etc/bind/named.conf.options or named.conf.local
dnssec-policy "standard" {
keys {
ksk key-directory lifetime unlimited algorithm ecdsap256sha256;
zsk key-directory lifetime 90d algorithm ecdsap256sha256;
};
nsec3param iterations 0 optout no salt-length 0;
max-zone-ttl 86400;
zone-propagation-delay 300;
parent-ds-ttl 3600;
parent-propagation-delay 7200;
};
zone "example.com" {
type master;
file "/etc/bind/zones/db.example.com";
key-directory "/etc/bind/keys/example.com";
dnssec-policy "standard";
inline-signing yes;
allow-transfer { 203.0.113.11; };
};
With
dnssec-policy, BIND automatically generates keys, signs the zone, re-signs before expiration, and handles ZSK rollovers on the defined schedule.
sudo named-checkconf
sudo systemctl restart named
sudo rndc signing -list example.com
Step 9 — Extract and Submit the DS Record to Your Registrar
The DS (Delegation Signer) record links your signed zone to the parent zone (e.g.,
.com). Extract it from the KSK:
sudo dnssec-dsfromkey /etc/bind/keys/example.com/Kexample.com.+013+12345.key
Output example:
example.com. IN DS 12345 13 2 E2D3C916F6DEEAC73294E8268FB5885044A833FC5459588F4A9184CFC41A5766
The fields are: Key Tag, Algorithm, Digest Type (2 = SHA-256), and Digest. Log in to your registrar and add this DS record. Most registrars have a dedicated "DNSSEC" or "DS Records" section.
Tip: Always submit the SHA-256 digest (type 2). SHA-1 (type 1) is deprecated.
Step 10 — Verify DNSSEC Is Working
10a — Local verification with dig
dig @localhost example.com A +dnssec +short
You should see the A record followed by an RRSIG record. The
ad(Authenticated Data) flag in the header confirms validation:
dig @localhost example.com A +dnssec | grep flags
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
10b — Test from an external validating resolver
dig @8.8.8.8 example.com A +dnssec +multi
Look for the
adflag in the response. If present, Google's resolver validated your DNSSEC chain successfully.
10c — Online tools
- DNSViz:
https://dnsviz.net/d/example.com/dnssec/
- Verisign DNSSEC Analyzer:
https://dnssec-analyzer.verisignlabs.com/example.com
- ICANN DNSSEC check:
https://dnssec-debugger.verisignlabs.com/
Step 11 — Enable DNSSEC Validation on Your Recursive Resolver
If your BIND9 server also acts as a recursive resolver for clients, enable validation in
/etc/bind/named.conf.options:
options {
directory "/var/cache/bind";
recursion yes;
allow-recursion { 10.0.0.0/8; 172.16.0.0/12; 192.168.0.0/16; };
dnssec-validation auto;
listen-on { 127.0.0.1; 10.0.0.1; };
};
dnssec-validation auto;tells BIND to use the built-in root trust anchors (managed via
managed-keysor
trust-anchorsstatements that ship with the package). Reload:
sudo rndc reconfig
sudo rndc validation-status
Test validation is working by querying a known DNSSEC-signed domain:
dig @127.0.0.1 isc.org A +dnssec
# Should return ad flag
Test a domain with a deliberately broken signature:
dig @127.0.0.1 dnssec-failed.org A
# Should return SERVFAIL
Step 12 — Key Rollover Strategy
Keys must be rotated periodically. Here is a practical rollover plan:
ZSK Rollover (Pre-Publish Method)
- Pre-publish: Generate new ZSK and add it to the zone (both old and new ZSK are published).
- Wait for the old DNSKEY TTL to expire (so all caches have the new key).
- Activate: Sign the zone with the new ZSK.
- Wait for the old RRSIG TTL to expire.
- Remove: Delete the old ZSK from the zone.
With
dnssec-policy, this is fully automated based on the
lifetimeyou configured (e.g.,
90d).
KSK Rollover (Double-DS Method)
- Generate a new KSK and add it to the zone.
- Compute the new DS record and submit it to your registrar alongside the old DS.
- Wait for the parent zone TTL to expire.
- Remove the old KSK from your zone.
- Remove the old DS record from the registrar.
Monitor the rollover with:
sudo rndc signing -list example.com
sudo rndc dnssec -status example.com # BIND 9.18+
Step 13 — Monitoring and Alerting
Set up monitoring to catch signature expiration or validation failures:
# Check RRSIG expiry with a script
RRSIG_EXPIRY=$(dig @localhost example.com RRSIG +short | head -1 | awk '{print $5}')
EXPIRY_EPOCH=$(date -d "$RRSIG_EXPIRY" +%s 2>/dev/null || date -j -f "%Y%m%d%H%M%S" "$RRSIG_EXPIRY" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))
if [ "$DAYS_LEFT" -lt 7 ]; then
echo "WARNING: RRSIG for example.com expires in $DAYS_LEFT days" | mail -s "DNSSEC Alert" admin@example.com
fi
Add this to a cron job running daily:
sudo crontab -e
# Add:
0 6 * * * /usr/local/bin/check-dnssec-expiry.sh
Step 14 — Troubleshooting Common DNSSEC Issues
Problem: SERVFAIL responses after enabling DNSSEC
- Check that the DS record at the registrar matches your KSK exactly.
- Verify system clock is synchronised:
timedatectl status
- Check signatures have not expired:
dig @localhost example.com RRSIG
- Review BIND logs:
sudo journalctl -u named -f
Problem: "no valid RRSIG" in logs
- Re-sign the zone or verify
inline-signing yes;
is active. - Ensure key files have correct ownership (
bind:bind
) and permissions.
Problem: Parent zone does not have DS record
- DNSSEC will not validate until the DS is in the parent. Use
dig example.com DS
to check. - Some registrars take 24–48 hours to propagate DS records.
Useful debugging commands
# Check the full chain of trust
delv @127.0.0.1 example.com A +rtrace
# Verify zone file syntax
sudo named-checkzone -i full example.com /etc/bind/zones/db.example.com.signed
# Flush resolver cache to test fresh
sudo rndc flush
Complete Configuration Summary
Here is the final
/etc/bind/named.conf.localusing the modern
dnssec-policyapproach:
dnssec-policy "standard" {
keys {
ksk key-directory lifetime unlimited algorithm ecdsap256sha256;
zsk key-directory lifetime 90d algorithm ecdsap256sha256;
};
nsec3param iterations 0 optout no salt-length 0;
max-zone-ttl 86400;
zone-propagation-delay 300;
parent-ds-ttl 3600;
parent-propagation-delay 7200;
};
zone "example.com" {
type master;
file "/etc/bind/zones/db.example.com";
key-directory "/etc/bind/keys/example.com";
dnssec-policy "standard";
inline-signing yes;
allow-transfer { 203.0.113.11; };
also-notify { 203.0.113.11; };
};
And
/etc/bind/named.conf.optionsfor a combined authoritative + recursive setup:
options {
directory "/var/cache/bind";
recursion yes;
allow-recursion { 10.0.0.0/8; 172.16.0.0/12; 192.168.0.0/16; localhost; };
dnssec-validation auto;
listen-on { any; };
listen-on-v6 { any; };
allow-query { any; };
forwarders { };
auth-nxdomain no;
};
Security Best Practices for DNSSEC
- Use ECDSAP256SHA256 (algorithm 13) — smaller keys, faster verification, widely supported.
- Use NSEC3 with zero iterations and no salt — RFC 9276 recommends this for optimal performance and compatibility.
- Protect private keys — restrict permissions to
bind:bind
with mode 640. - Monitor signature expiry — expired RRSIG records cause total zone failure for validating resolvers.
- Keep system time accurate — DNSSEC signatures have inception and expiration timestamps.
- Test before going live — use
delv
, DNSViz, and Verisign analyzers before submitting the DS to the parent. - Have a rollback plan — know how to remove the DS record from the registrar to disable DNSSEC quickly if needed.
- Backup your keys — store encrypted key backups off-server. Losing a KSK without the corresponding DS removal causes a broken chain.
Frequently Asked Questions (FAQ)
Q1: What is DNSSEC and why do I need it?
DNSSEC adds cryptographic signatures to DNS records, allowing resolvers to verify that DNS responses have not been tampered with. It protects against cache poisoning, DNS spoofing, and man-in-the-middle attacks on DNS queries.
Q2: Does DNSSEC encrypt DNS traffic?
No. DNSSEC provides authentication and integrity but not confidentiality. DNS queries and responses remain in plaintext. For encryption, you need DNS-over-TLS (DoT) or DNS-over-HTTPS (DoH) in addition to DNSSEC.
Q3: What happens if my DNSSEC signatures expire?
Validating resolvers will return SERVFAIL for all queries to your zone. Your domain will effectively go offline for users behind validating resolvers (including Google Public DNS, Cloudflare 1.1.1.1, and most ISP resolvers). This is why automated re-signing with
inline-signingor
dnssec-policyis critical.
Q4: Which DNSSEC algorithm should I use?
Use ECDSAP256SHA256 (algorithm 13). It is recommended by RFC 8624, produces small signatures (64 bytes vs 256 bytes for RSA-2048), and is supported by all modern resolvers. Avoid RSA-SHA1 (algorithm 5) and DSA (algorithm 3) as they are deprecated.
Q5: What is the difference between NSEC and NSEC3?
NSEC proves that a DNS name does not exist by listing the next existing name, which allows zone enumeration (walking the entire zone). NSEC3 hashes the names, making enumeration significantly harder. Use NSEC3 with zero iterations and no salt per RFC 9276.
Q6: How often should I rotate DNSSEC keys?
ZSK: every 1–3 months. KSK: every 1–2 years, or when algorithm changes are needed. With BIND9's
dnssec-policy, ZSK rollovers are fully automated. KSK rollovers require DS record updates at the registrar.
Q7: Can I use DNSSEC with dynamic DNS updates?
Yes. When using
inline-signing yes;, BIND maintains a separate signed version of the zone. Dynamic updates modify the unsigned zone, and BIND automatically re-signs the affected records.
Q8: How do I disable DNSSEC if something goes wrong?
First, remove the DS record from your registrar and wait for the parent zone TTL to expire (typically 24–48 hours). Then remove the
dnssec-policyor
auto-dnssecdirectives from your BIND configuration, reload BIND, and serve the unsigned zone file.
Q9: Does DNSSEC work with secondary (slave) name servers?
Yes. The primary server signs the zone and transfers the signed zone (including RRSIG, DNSKEY, and NSEC/NSEC3 records) to secondaries via AXFR/IXFR. Secondary servers do not need access to the private keys.
Q10: How do I verify the full DNSSEC chain of trust from root to my domain?
Use the
delvcommand (DNSSEC-aware replacement for dig):
delv @8.8.8.8 example.com A +rtrace. For visual chain analysis, use DNSViz at
https://dnsviz.net/. Both tools trace the chain from the root zone through TLD to your domain and report any broken links.
