Setting Up Dynamic DNS Updates with BIND and TSIG Authentication
Dynamic DNS (DDNS) allows automated systems to update DNS records in real-time. This is essential for DHCP servers registering client hostnames, Kubernetes services updating their endpoints, or any system that needs programmatic DNS record management. BIND supports secure dynamic updates using TSIG (Transaction Signature) authentication.
How Dynamic DNS Works
Dynamic DNS updates use the DNS UPDATE protocol (RFC 2136). A client sends an update request to the authoritative DNS server, which validates the request and modifies the zone file. TSIG keys provide cryptographic authentication to ensure only authorized clients can make changes.
The process:
- Client generates an update request
- Client signs the request with a TSIG key
- Server receives and validates the signature
- Server applies the update to the zone
- Server responds with success or error
Generating TSIG Keys
Create a dedicated key for dynamic updates:
tsig-keygen -a hmac-sha256 ddns-key
Output:
key "ddns-key" {
algorithm hmac-sha256;
secret "X7s8F2pL+Kd9mN1vQ4rT6wY0zA3cE5hG7jU2nM4xB8=";
};
For different clients, create separate keys:
tsig-keygen -a hmac-sha256 dhcp-update-key > /etc/named/keys/dhcp.key
tsig-keygen -a hmac-sha256 k8s-update-key > /etc/named/keys/kubernetes.key
tsig-keygen -a hmac-sha256 admin-update-key > /etc/named/keys/admin.key
Secure the key files:
chmod 640 /etc/named/keys/*.key
chown root:named /etc/named/keys/*.key
Configuring BIND for Dynamic Updates
Basic Update Configuration
Include the key and configure the zone:
// /etc/named.conf
include "/etc/named/keys/ddns.key";
zone "dynamic.example.com" {
type primary;
file "/var/named/dynamic/dynamic.example.com.zone";
allow-update { key "ddns-key"; };
// Journal file for tracking updates
journal "/var/named/dynamic/dynamic.example.com.zone.jnl";
};
Multiple Keys with Different Permissions
Grant different access levels to different keys:
include "/etc/named/keys/dhcp.key";
include "/etc/named/keys/kubernetes.key";
include "/etc/named/keys/admin.key";
zone "internal.example.com" {
type primary;
file "/var/named/zones/internal.example.com.zone";
// DHCP can only update specific record types
update-policy {
grant dhcp-update-key name *.internal.example.com A AAAA;
grant dhcp-update-key name *.internal.example.com TXT;
};
};
zone "k8s.example.com" {
type primary;
file "/var/named/zones/k8s.example.com.zone";
// Kubernetes can manage its subdomain
update-policy {
grant k8s-update-key subdomain k8s.example.com ANY;
};
};
zone "example.com" {
type primary;
file "/var/named/zones/example.com.zone";
// Admin has full control
update-policy {
grant admin-update-key zonesub ANY;
};
};
Update Policy Rules
BIND supports granular update policies:
update-policy {
// Grant based on key name
grant keyname ruletype name [types];
};
Rule types:
name- Exact name matchsubdomain- Name and all names below itwildcard- Pattern match with wildcardszonesub- Any name in the zoneself- Client can update its own name (based on key name)selfsub- Client can update its name and belowselfwild- Client can update wildcards of its name
Examples:
update-policy {
// DHCP can add A records for any host in clients subdomain
grant dhcp-key subdomain clients.example.com A AAAA PTR;
// Let-encrypt can update TXT records for ACME challenges
grant acme-key name _acme-challenge.example.com TXT;
// Monitoring can update health check records
grant monitoring-key name health.example.com A TXT;
// Self-registration: key name matches the record name
grant self self * A AAAA;
};
Directory and File Permissions
BIND needs write access to zone directories for journal files:
# Create directory for dynamic zones
mkdir -p /var/named/dynamic
# Set ownership
chown named:named /var/named/dynamic
chmod 750 /var/named/dynamic
# If using SELinux
semanage fcontext -a -t named_zone_t "/var/named/dynamic(/.*)?"
restorecon -Rv /var/named/dynamic
Sending Dynamic Updates
Using nsupdate
The nsupdate command sends dynamic updates:
# Interactive mode
nsupdate -k /etc/named/keys/ddns.key
> server ns1.example.com
> zone dynamic.example.com
> update add newhost.dynamic.example.com 300 A 192.168.1.100
> send
> quit
From a script:
nsupdate -k /etc/named/keys/ddns.key << EOF
server ns1.example.com
zone dynamic.example.com
update delete oldhost.dynamic.example.com A
update add newhost.dynamic.example.com 300 A 192.168.1.100
send
EOF
Update Operations
# Add a record
update add hostname.example.com 300 A 192.168.1.10
# Delete specific record
update delete hostname.example.com A 192.168.1.10
# Delete all records of a type
update delete hostname.example.com A
# Delete all records for a name
update delete hostname.example.com
# Update (delete + add)
update delete hostname.example.com A
update add hostname.example.com 300 A 192.168.1.20
# Add multiple records
update add hostname.example.com 300 A 192.168.1.10
update add hostname.example.com 300 A 192.168.1.11
# Add different record types
update add hostname.example.com 300 A 192.168.1.10
update add hostname.example.com 300 AAAA 2001:db8::10
update add hostname.example.com 300 TXT "v=spf1 -all"
Prerequisites (Conditional Updates)
Require certain conditions before applying updates:
nsupdate -k /etc/named/keys/ddns.key << EOF
server ns1.example.com
zone example.com
# Only add if record doesn't exist
prereq nxdomain newhost.example.com
update add newhost.example.com 300 A 192.168.1.10
send
# Only update if record exists
prereq yxdomain oldhost.example.com
update delete oldhost.example.com A
update add oldhost.example.com 300 A 192.168.1.20
send
# Only update if specific record exists
prereq yxrrset oldhost.example.com A 192.168.1.10
update delete oldhost.example.com A
update add oldhost.example.com 300 A 192.168.1.20
send
EOF
Integrating with ISC DHCP Server
Configure ISC DHCP to update DNS when leases are assigned:
DHCP Server Configuration
# /etc/dhcp/dhcpd.conf
ddns-updates on;
ddns-update-style interim;
ddns-domainname "internal.example.com";
ddns-rev-domainname "in-addr.arpa";
# Update both forward and reverse zones
update-static-leases on;
# Key for DNS updates
key "dhcp-key" {
algorithm hmac-sha256;
secret "your-secret-from-tsig-keygen";
};
# Forward zone
zone internal.example.com. {
primary 192.168.1.10;
key dhcp-key;
}
# Reverse zone
zone 1.168.192.in-addr.arpa. {
primary 192.168.1.10;
key dhcp-key;
}
# Subnet definition
subnet 192.168.1.0 netmask 255.255.255.0 {
range 192.168.1.100 192.168.1.200;
option routers 192.168.1.1;
option domain-name "internal.example.com";
option domain-name-servers 192.168.1.10;
# Clients provide their hostname
ddns-hostname = pick-first-value(
option fqdn.hostname,
option host-name,
concat("dhcp-", binary-to-ascii(10, 8, "-", leased-address))
);
}
BIND Configuration for DHCP
// Forward zone
zone "internal.example.com" {
type primary;
file "/var/named/dynamic/internal.example.com.zone";
update-policy {
grant dhcp-key subdomain internal.example.com A AAAA DHCID;
};
};
// Reverse zone
zone "1.168.192.in-addr.arpa" {
type primary;
file "/var/named/dynamic/192.168.1.rev";
update-policy {
grant dhcp-key subdomain 1.168.192.in-addr.arpa PTR;
};
};
Reverse DNS Updates
Configure reverse zones for PTR records:
nsupdate -k /etc/named/keys/ddns.key << EOF
server ns1.example.com
zone 1.168.192.in-addr.arpa
update add 100.1.168.192.in-addr.arpa 300 PTR hostname.example.com.
send
EOF
Script for simultaneous forward and reverse updates:
#!/bin/bash
HOSTNAME="$1"
IP="$2"
ZONE="internal.example.com"
KEY="/etc/named/keys/ddns.key"
SERVER="ns1.example.com"
TTL=300
# Generate reverse zone from IP
IFS='.' read -ra OCTETS <<< "$IP"
REVERSE="${OCTETS[3]}.${OCTETS[2]}.${OCTETS[1]}.${OCTETS[0]}.in-addr.arpa"
REVZONE="${OCTETS[2]}.${OCTETS[1]}.${OCTETS[0]}.in-addr.arpa"
# Update forward zone
nsupdate -k "$KEY" << EOF
server $SERVER
zone $ZONE
update delete ${HOSTNAME}.${ZONE} A
update add ${HOSTNAME}.${ZONE} $TTL A $IP
send
EOF
# Update reverse zone
nsupdate -k "$KEY" << EOF
server $SERVER
zone $REVZONE
update delete $REVERSE PTR
update add $REVERSE $TTL PTR ${HOSTNAME}.${ZONE}.
send
EOF
echo "Updated: ${HOSTNAME}.${ZONE} -> $IP"
Managing Journal Files
BIND stores dynamic updates in journal files before merging them with the zone file.
Freeze and Thaw
To manually edit a dynamic zone:
# Freeze zone (syncs journal to zone file)
rndc freeze example.com
# Edit the zone file manually
vim /var/named/dynamic/example.com.zone
# Update serial number!
# Thaw zone (re-enables dynamic updates)
rndc thaw example.com
Sync Without Freezing
# Sync journal to zone file without freezing
rndc sync example.com
# Sync and clear journal
rndc sync -clean example.com
Viewing Zone Contents
# Dump zone including dynamic updates
rndc dumpdb -zones
cat /var/named/data/named_dump.db | grep -A100 "example.com"
# Or view via dig
dig @localhost example.com AXFR
Troubleshooting
Enable Update Logging
logging {
channel update_log {
file "/var/log/named/update.log" versions 5 size 10m;
severity debug;
print-time yes;
print-category yes;
};
category update { update_log; };
category update-security { update_log; };
};
Common Errors
REFUSED
; TSIG error with server: tsig verify failure
update failed: REFUSED
Causes:
- Key not configured on server
- Key name mismatch
- Secret mismatch
- Update policy doesn't allow the operation
NOTAUTH
update failed: NOTAUTH
Causes:
- Server is not authoritative for the zone
- Zone name incorrect
SERVFAIL
update failed: SERVFAIL
Causes:
- Zone file permission issues
- Journal file permission issues
- Filesystem full
Debug Mode
# Verbose nsupdate
nsupdate -d -k /etc/named/keys/ddns.key << EOF
server ns1.example.com
zone example.com
update add test.example.com 300 A 192.168.1.99
send
EOF
Check Permissions
# Zone file and directory permissions
ls -la /var/named/dynamic/
# SELinux context
ls -laZ /var/named/dynamic/
# Check for SELinux denials
ausearch -m AVC -ts recent | grep named
Security Considerations
Limit Update Scope
Never use allow-update { any; }. Always restrict updates:
// Bad - anyone can update
allow-update { any; };
// Good - only authenticated clients
allow-update { key "update-key"; };
// Better - granular policy
update-policy {
grant update-key subdomain clients.example.com A AAAA;
};
Separate Keys per Service
Each service should have its own key with minimum required permissions:
update-policy {
// DHCP only manages client records
grant dhcp-key subdomain clients.internal.example.com A AAAA PTR DHCID;
// Let's Encrypt only manages ACME challenges
grant acme-key name _acme-challenge.example.com TXT;
// Kubernetes only manages its namespace
grant k8s-key subdomain k8s.example.com A AAAA SRV;
};
Protect Key Files
chmod 400 /etc/named/keys/*.key
chown root:named /etc/named/keys/*.key
# For client systems, only the specific key they need
chmod 400 /etc/dhcp/dhcp-key.key
chown root:dhcpd /etc/dhcp/dhcp-key.key
Conclusion
Dynamic DNS with TSIG authentication enables secure, automated DNS record management. Combined with granular update policies, you can safely integrate DNS updates into your DHCP infrastructure, container orchestration, and automation workflows.
The next post will cover integrating BIND with Kubernetes external-dns for automated service discovery and DNS management.