Signing Your DNS Zones with DNSSEC on BIND Authoritative Servers
After configuring DNSSEC validation on your resolvers, the next step is signing your own authoritative zones. This post covers the complete process of implementing DNSSEC on BIND authoritative servers, from key generation to zone signing and key rollovers.
Understanding DNSSEC Keys
DNSSEC uses two types of cryptographic keys:
Key Signing Key (KSK)
- Signs the DNSKEY RRset (the zone's public keys)
- Longer lifetime (typically 1-2 years)
- Larger key size for stronger security
- Its hash (DS record) is published in the parent zone
Zone Signing Key (ZSK)
- Signs all other records in the zone
- Shorter lifetime (typically 1-3 months)
- Smaller key size for performance
- Rolled more frequently
Choosing DNSSEC Algorithms
Modern BIND supports several algorithms:
| Algorithm | Number | Recommendation |
|---|---|---|
| RSASHA256 | 8 | Good, widely supported |
| RSASHA512 | 10 | Good, wider margins |
| ECDSAP256SHA256 | 13 | Recommended, smaller keys |
| ECDSAP384SHA384 | 14 | Strong, larger signatures |
| ED25519 | 15 | Best, smallest signatures |
For new deployments, use ECDSAP256SHA256 (13) or ED25519 (15) for best performance and security.
Manual DNSSEC Configuration
Generating Keys Manually
Create the key directory:
mkdir -p /var/cache/bind/keys
chown bind:bind /var/cache/bind/keys
chmod 700 /var/cache/bind/keys
Generate the KSK:
cd /var/cache/bind/keys
# Generate KSK with ECDSAP256SHA256
dnssec-keygen -a ECDSAP256SHA256 -f KSK -n ZONE example.com
# Or with RSA (2048-bit minimum, 4096 recommended for KSK)
dnssec-keygen -a RSASHA256 -b 4096 -f KSK -n ZONE example.com
Generate the ZSK:
# Generate ZSK with ECDSAP256SHA256
dnssec-keygen -a ECDSAP256SHA256 -n ZONE example.com
# Or with RSA (2048-bit)
dnssec-keygen -a RSASHA256 -b 2048 -n ZONE example.com
This creates four files per key:
Kexample.com.+013+12345.key- Public key (DNSKEY record)Kexample.com.+013+12345.private- Private key
Manual Zone Signing
Include keys in your zone file:
; /var/cache/bind/zones/db.example.com
$TTL 86400
$INCLUDE /var/cache/bind/keys/Kexample.com.+013+12345.key
$INCLUDE /var/cache/bind/keys/Kexample.com.+013+67890.key
@ IN SOA ns1.example.com. admin.example.com. (
2024011001 ; Serial
3600 ; Refresh
1800 ; Retry
604800 ; Expire
86400 ) ; Minimum TTL
IN NS ns1.example.com.
IN NS ns2.example.com.
IN MX 10 mail.example.com.
IN A 192.168.1.10
ns1 IN A 192.168.1.1
ns2 IN A 192.168.1.2
mail IN A 192.168.1.20
www IN A 192.168.1.10
Sign the zone:
# Sign zone with automatic NSEC3
dnssec-signzone -A -3 $(head -c 16 /dev/urandom | xxd -p) \
-N INCREMENT -o example.com -t \
-K /var/cache/bind/keys \
/var/cache/bind/zones/db.example.com
This creates db.example.com.signed containing:
- RRSIG records (signatures)
- NSEC3 records (authenticated denial of existence)
- NSEC3PARAM record
Automated DNSSEC with dnssec-policy
BIND 9.16+ supports automatic key management with dnssec-policy. This is the recommended approach:
Define DNSSEC Policies
// /etc/bind/named.conf.options
dnssec-policy "standard" {
// Key algorithm and parameters
keys {
// KSK: ECDSA P-256, unlimited lifetime (manual rollover)
ksk key-directory lifetime unlimited algorithm ecdsap256sha256;
// ZSK: ECDSA P-256, 90-day lifetime (auto rollover)
zsk key-directory lifetime 90d algorithm ecdsap256sha256;
};
// Timing parameters
dnskey-ttl 3600; // TTL for DNSKEY records
publish-safety 1h; // Safety margin for publishing
retire-safety 1h; // Safety margin for retiring
purge-keys 90d; // Remove old keys after 90 days
// Signature parameters
signatures-refresh 5d; // Re-sign 5 days before expiry
signatures-validity 14d; // Signatures valid for 14 days
signatures-validity-dnskey 14d;
// NSEC3 parameters (authenticated denial)
nsec3param iterations 0 optout no salt-length 0;
// Key storage
key-directory "/var/cache/bind/keys";
};
// More conservative policy for critical zones
dnssec-policy "critical" {
keys {
ksk key-directory lifetime unlimited algorithm ecdsap384sha384;
zsk key-directory lifetime 30d algorithm ecdsap384sha384;
};
dnskey-ttl 1800;
signatures-refresh 3d;
signatures-validity 7d;
signatures-validity-dnskey 7d;
nsec3param iterations 0 optout no salt-length 0;
key-directory "/var/cache/bind/keys";
};
// Default policy (built-in)
// Uses ECDSAP256SHA256, 3-year KSK, 13-month ZSK
Apply Policy to Zones
// /etc/bind/named.conf.local
zone "example.com" {
type primary;
file "/var/cache/bind/zones/db.example.com";
dnssec-policy "standard";
inline-signing yes; // Sign dynamically, keep unsigned source
key-directory "/var/cache/bind/keys";
};
zone "critical.example.com" {
type primary;
file "/var/cache/bind/zones/db.critical.example.com";
dnssec-policy "critical";
inline-signing yes;
key-directory "/var/cache/bind/keys";
};
Inline Signing
With inline-signing yes, BIND:
- Keeps your original zone file unchanged
- Creates a signed version automatically
- Re-signs when signatures approach expiry
- Handles key rollovers automatically
Publishing the DS Record
Once your zone is signed, you must publish the DS record in the parent zone to complete the chain of trust.
Generate DS Record
# Generate DS records from your KSK
dnssec-dsfromkey -2 /var/cache/bind/keys/Kexample.com.+013+12345.key
# Output:
# example.com. IN DS 12345 13 2 E2D3C916F6DED0ED3D9A8E6C1C0A2ED7...
The DS record contains:
- Key tag (12345)
- Algorithm (13 = ECDSAP256SHA256)
- Digest type (2 = SHA-256)
- Digest (hash of the KSK)
Submit DS Record to Registrar
For domains registered with a registrar:
- Log into your registrar's control panel
- Find DNS/DNSSEC settings
- Add the DS record with:
- Key Tag: 12345
- Algorithm: 13
- Digest Type: 2
- Digest: (the hash value)
For subzones under your control, add the DS record to the parent zone:
; In parent zone
subdomain IN DS 12345 13 2 E2D3C916F6DED0ED3D9A8E6C1C0A2ED7...
NSEC vs NSEC3
DNSSEC provides authenticated denial of existence through NSEC or NSEC3 records.
NSEC (Original)
// Enable NSEC (default)
dnssec-policy "nsec-policy" {
keys {
ksk lifetime unlimited algorithm ecdsap256sha256;
zsk lifetime 90d algorithm ecdsap256sha256;
};
// No nsec3param = use NSEC
};
Pros: Simpler, smaller responses Cons: Allows zone enumeration (walking)
NSEC3 (Recommended)
// Enable NSEC3
dnssec-policy "nsec3-policy" {
keys {
ksk lifetime unlimited algorithm ecdsap256sha256;
zsk lifetime 90d algorithm ecdsap256sha256;
};
// NSEC3 parameters
// iterations: 0 recommended (RFC 9276)
// optout: no (sign all records)
// salt-length: 0 recommended (RFC 9276)
nsec3param iterations 0 optout no salt-length 0;
};
Pros: Prevents zone enumeration Cons: Slightly larger responses, more CPU
Key Rollover Procedures
Automatic ZSK Rollover
With dnssec-policy, ZSK rollovers happen automatically. Monitor with:
# Check key states
rndc dnssec -status example.com
# View upcoming rollovers
rndc signing -list example.com
Manual KSK Rollover (Double-DS Method)
KSK rollovers require coordination with the parent zone:
Step 1: Generate new KSK
# Generate successor KSK
rndc dnssec -rollover -key 12345 example.com
# Or manually
dnssec-keygen -a ECDSAP256SHA256 -f KSK -n ZONE example.com
Step 2: Add new DS to parent
# Generate DS for new key
dnssec-dsfromkey /var/cache/bind/keys/Kexample.com.+013+NEW_ID.key
# Submit new DS to registrar (keep old DS)
Step 3: Wait for propagation
Wait at least 2x the TTL of the DS record in the parent zone.
Step 4: Complete rollover
# Retire old KSK
rndc dnssec -checkds -key 12345 published example.com
# After TTL expires, remove old DS from parent
Step 5: Remove old DS from parent
Remove the old DS record from your registrar.
Monitoring DNSSEC Health
Check Zone Signatures
#!/bin/bash
# check-dnssec.sh - Monitor DNSSEC signature expiry
ZONE="example.com"
WARN_DAYS=7
# Get signature expiry from zone
EXPIRY=$(dig +dnssec @localhost $ZONE SOA | \
grep RRSIG | \
awk '{print $9}' | \
head -1)
if [ -n "$EXPIRY" ]; then
# Parse DNSSEC timestamp (YYYYMMDDHHMMSS)
EXPIRY_EPOCH=$(date -d "${EXPIRY:0:8} ${EXPIRY:8:2}:${EXPIRY:10:2}:${EXPIRY:12:2}" +%s 2>/dev/null)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))
echo "Zone: $ZONE"
echo "Signature expires: $EXPIRY"
echo "Days remaining: $DAYS_LEFT"
if [ $DAYS_LEFT -lt $WARN_DAYS ]; then
echo "WARNING: Signatures expiring soon!"
exit 1
fi
else
echo "ERROR: Could not retrieve DNSSEC signatures for $ZONE"
exit 2
fi
Verify Chain of Trust
# Full chain verification
delv @8.8.8.8 example.com SOA +rtrace
# Check DS record matches
dig DS example.com @parent-ns.tld
dnssec-dsfromkey /var/cache/bind/keys/Kexample.com.+013+12345.key
# Verify signatures
dnssec-verify -o example.com /var/cache/bind/zones/db.example.com.signed
Online Validation Tools
Use these services to verify your DNSSEC deployment:
- DNSViz: https://dnsviz.net/
- Verisign DNSSEC Analyzer: https://dnssec-analyzer.verisignlabs.com/
- SIDN DNSSEC Test: https://check.sidnlabs.nl/
Complete Configuration Example
Here's a production-ready DNSSEC configuration:
// /etc/bind/named.conf.options
options {
directory "/var/cache/bind";
// DNSSEC for authoritative responses
dnssec-validation auto;
// Key and managed-keys directory
key-directory "/var/cache/bind/keys";
managed-keys-directory "/var/cache/bind/managed-keys";
// Logging
querylog yes;
};
// Standard DNSSEC policy
dnssec-policy "production" {
keys {
ksk key-directory lifetime unlimited algorithm ecdsap256sha256;
zsk key-directory lifetime P90D algorithm ecdsap256sha256;
};
// Timing
dnskey-ttl PT1H;
publish-safety PT1H;
retire-safety PT1H;
purge-keys P90D;
// Signatures
signatures-refresh P5D;
signatures-validity P14D;
signatures-validity-dnskey P14D;
// NSEC3 (RFC 9276 recommendations)
nsec3param iterations 0 optout no salt-length 0;
};
logging {
channel dnssec_log {
file "/var/log/named/dnssec.log" versions 3 size 10m;
severity info;
print-time yes;
print-severity yes;
};
category dnssec { dnssec_log; };
};
// /etc/bind/named.conf.local
zone "example.com" {
type primary;
file "/var/cache/bind/zones/db.example.com";
dnssec-policy "production";
inline-signing yes;
// Allow zone transfers to secondaries
allow-transfer { key "transfer-key"; };
also-notify { 192.168.1.2; };
};
zone "1.168.192.in-addr.arpa" {
type primary;
file "/var/cache/bind/zones/db.192.168.1";
dnssec-policy "production";
inline-signing yes;
allow-transfer { key "transfer-key"; };
};
Troubleshooting DNSSEC
Common Issues
Signatures expired:
# Check signature validity
rndc signing -list example.com
# Force re-signing
rndc sign example.com
DS record mismatch:
# Compare DS records
dig DS example.com @parent-ns
dnssec-dsfromkey Kexample.com.+013+12345.key
# Ensure key tag, algorithm, and digest match
Key not found:
# Check key files exist and permissions
ls -la /var/cache/bind/keys/
chown bind:bind /var/cache/bind/keys/*
chmod 600 /var/cache/bind/keys/*.private
Zone won't load:
# Check configuration
named-checkconf
named-checkzone example.com /var/cache/bind/zones/db.example.com
# Check logs
tail -f /var/log/named/dnssec.log
Security Best Practices
- Protect private keys - Use proper file permissions (600) and consider HSM for high-security deployments
- Monitor signature expiry - Set up alerts for signatures approaching expiry
- Test rollovers - Practice key rollovers in a test environment first
- Use NSEC3 - Prevents zone enumeration attacks
- Keep BIND updated - Security fixes often affect DNSSEC
- Document procedures - Especially for manual KSK rollovers
- Backup keys securely - Store encrypted backups offline
Conclusion
DNSSEC on authoritative servers completes the chain of trust for your domains. Using dnssec-policy with inline signing automates most of the complexity, while proper monitoring ensures your zones remain securely signed.
Key takeaways:
- Use ECDSAP256SHA256 or ED25519 algorithms
- Enable inline-signing for automatic signature maintenance
- Use NSEC3 to prevent zone enumeration
- Monitor signature expiry proactively
- Plan and test key rollovers carefully
The next post will cover DNS over TLS (DoT), adding transport encryption to your DNS infrastructure for privacy and security.