What Is Dynamic DNS (DDNS)?
Dynamic DNS (DDNS) allows DNS records to be updated automatically without manually editing zone files and issuing a reload. Instead of static zone file entries, DDNS enables clients, DHCP servers, or administration scripts to push record changes directly to the authoritative name server in real time. This is essential in environments where IP addresses change frequently — DHCP-assigned workstations, containerized workloads, VPN endpoints, and cloud instances with ephemeral addresses all benefit from automatic DNS registration and deregistration.
BIND9 supports dynamic DNS updates as defined in RFC 2136, which specifies the DNS UPDATE protocol. Updates are transmitted as standard DNS messages to the authoritative server, which applies changes directly to its in-memory zone data and journals them to a
.jnlfile on disk. The result is a live, always-current zone with no manual intervention required.
How BIND9 Handles Dynamic Updates Internally
When BIND9 receives a valid dynamic update, it applies the change to the in-memory zone immediately and appends a journal entry to a file named
zonename.jnlalongside the zone file on disk. The original zone file is not rewritten immediately — BIND periodically freezes the zone, merges the accumulated journal entries into the zone file, and thaws it. This design means that the on-disk zone file can lag behind the live zone state, and you should never manually edit a zone file while an active journal exists for it.
Dynamic updates can be controlled by three mechanisms:
- IP address ACL — using
allow-update
with a network address list - TSIG key — using
allow-update
with a named TSIG key (recommended for production) - Update policy rules — using the more granular
update-policy
directive for fine-grained per-key, per-record-type control
Prerequisites
Before proceeding, ensure the following are in place on
sw-infrarunbook-01:
- BIND9 installed and authoritative for the target zone
- A working forward zone for
solvethenetwork.com
- A working reverse zone for
10.10.10.in-addr.arpa
- Root or sudo access on the DNS server
- The
dnsutils
package installed (providesnsupdate
anddig
)
apt install dnsutils bind9utils -y
Step 1 — Generate a TSIG Key for Dynamic Updates
Rather than permitting updates based on source IP alone, production deployments must require TSIG authentication. Generate a dedicated TSIG key using
tsig-keygen, which ships with BIND9:
tsig-keygen -a hmac-sha256 ddns-key.solvethenetwork.com
This outputs a ready-to-include key block. Redirect it directly to a key file:
tsig-keygen -a hmac-sha256 ddns-key.solvethenetwork.com \
> /etc/bind/ddns-key.solvethenetwork.com.key
The resulting file looks like the following:
key "ddns-key.solvethenetwork.com" {
algorithm hmac-sha256;
secret "xQ8tV7mNpL3kJzRwYcHbOdFsAeGiUl9nK2vX5qT1yM4=";
};
Lock down permissions so only root and the bind group can read the key:
chown root:bind /etc/bind/ddns-key.solvethenetwork.com.key
chmod 640 /etc/bind/ddns-key.solvethenetwork.com.key
Step 2 — Configure BIND9 to Accept Dynamic Updates
Edit
/etc/bind/named.conf.localto include the key file and configure both the forward and reverse zones to accept authenticated updates. Note that dynamic zone files must reside in a directory writable by the
binduser —
/var/lib/bind/is the correct location on Debian and Ubuntu systems.
include "/etc/bind/ddns-key.solvethenetwork.com.key";
zone "solvethenetwork.com" {
type master;
file "/var/lib/bind/db.solvethenetwork.com";
allow-update { key "ddns-key.solvethenetwork.com"; };
};
zone "10.10.10.in-addr.arpa" {
type master;
file "/var/lib/bind/db.10.10.10";
allow-update { key "ddns-key.solvethenetwork.com"; };
};
Key configuration points:
- The
include
directive must appear before the zone blocks that reference the key name. - Zone files in
/etc/bind/
are typically not writable by the bind user — BIND will fail to create journal files there. Always use/var/lib/bind/
for dynamic zones. - The
allow-update
directive here permits updates only from requests signed with the matching TSIG key, regardless of source IP.
Step 3 — Prepare the Zone Files
Create the forward zone file at
/var/lib/bind/db.solvethenetwork.com:
$TTL 3600
@ IN SOA sw-infrarunbook-01.solvethenetwork.com. infrarunbook-admin.solvethenetwork.com. (
2024031501 ; Serial
3600 ; Refresh
900 ; Retry
604800 ; Expire
300 ) ; Negative TTL
@ IN NS sw-infrarunbook-01.solvethenetwork.com.
sw-infrarunbook-01 IN A 10.10.10.1
Create the reverse zone file at
/var/lib/bind/db.10.10.10:
$TTL 3600
@ IN SOA sw-infrarunbook-01.solvethenetwork.com. infrarunbook-admin.solvethenetwork.com. (
2024031501 ; Serial
3600 ; Refresh
900 ; Retry
604800 ; Expire
300 ) ; Negative TTL
@ IN NS sw-infrarunbook-01.solvethenetwork.com.
1 IN PTR sw-infrarunbook-01.solvethenetwork.com.
Set ownership so BIND can write journal files alongside the zone files:
chown bind:bind /var/lib/bind/db.solvethenetwork.com
chown bind:bind /var/lib/bind/db.10.10.10
Validate and reload:
named-checkconf
named-checkzone solvethenetwork.com /var/lib/bind/db.solvethenetwork.com
named-checkzone 10.10.10.in-addr.arpa /var/lib/bind/db.10.10.10
systemctl reload bind9
Step 4 — Adding Records with nsupdate
nsupdateis the standard command-line tool for sending RFC 2136 DNS UPDATE messages. It reads commands from an input file (or interactively) and transmits a signed DNS UPDATE request to the server. Create an update script to add an A record and its PTR simultaneously:
server 10.10.10.1
zone solvethenetwork.com
update add webserver-01.solvethenetwork.com. 3600 A 10.10.10.50
send
zone 10.10.10.in-addr.arpa
update add 50.10.10.10.in-addr.arpa. 3600 PTR webserver-01.solvethenetwork.com.
send
Save this as
/tmp/ddns-add-host.txtand execute with TSIG authentication:
nsupdate -k /etc/bind/ddns-key.solvethenetwork.com.key /tmp/ddns-add-host.txt
A successful update exits silently with status 0. Verify the records were applied:
dig @10.10.10.1 webserver-01.solvethenetwork.com A +short
dig @10.10.10.1 -x 10.10.10.50 +short
Step 5 — Deleting Records with nsupdate
To remove a record, use the
update deletecommand. Omitting the RDATA deletes all records of that type at the specified name:
server 10.10.10.1
zone solvethenetwork.com
update delete webserver-01.solvethenetwork.com. A
send
zone 10.10.10.in-addr.arpa
update delete 50.10.10.10.in-addr.arpa. PTR
send
nsupdate -k /etc/bind/ddns-key.solvethenetwork.com.key /tmp/ddns-delete-host.txt
To delete a specific record value rather than all records of a type, include the full RDATA in the delete command:
update delete webserver-01.solvethenetwork.com. 3600 A 10.10.10.50
Step 6 — Updating an Existing Record
RFC 2136 DNS UPDATE has no native modify operation. To change an existing record value, delete the old entry and add the new one in a single atomic transaction:
server 10.10.10.1
zone solvethenetwork.com
update delete webserver-01.solvethenetwork.com. A
update add webserver-01.solvethenetwork.com. 3600 A 10.10.10.55
send
nsupdate -k /etc/bind/ddns-key.solvethenetwork.com.key /tmp/ddns-update-host.txt
Both operations in a single transaction are applied atomically — if the add fails, the delete is also rolled back, preventing record loss.
Using update-policy for Fine-Grained Access Control
The
allow-updatedirective is binary: a client that passes TSIG authentication can update any record type in the zone. For stricter production environments, replace it with the
update-policydirective, which allows per-key, per-record-type, per-name restrictions.
zone "solvethenetwork.com" {
type master;
file "/var/lib/bind/db.solvethenetwork.com";
update-policy {
grant ddns-key.solvethenetwork.com wildcard *.solvethenetwork.com. A AAAA TXT;
grant ddns-key.solvethenetwork.com name solvethenetwork.com. MX;
};
};
With this policy in place:
- The DDNS key may add or delete
A
,AAAA
, andTXT
records for any hostname undersolvethenetwork.com
- It may also update the
MX
record at the zone apex - It cannot modify
NS
,SOA
,CNAME
,SRV
, or any other record types — protecting critical zone infrastructure from accidental or malicious overwrites
The
wildcardmatch type grants access to names matching the pattern. Other match types include
name(exact match),
subdomain(name and all descendants), and
self(the key name itself, useful for GSS-TSIG/Kerberos-authenticated clients registering their own hostnames).
Integrating Dynamic DNS with ISC DHCP Server
One of the most common production uses of DDNS is automatic forward and reverse registration when clients receive DHCP leases. ISC DHCP can send RFC 2136 updates to BIND9 on behalf of DHCP clients — both servers must share the same TSIG key.
If the DHCP server runs on the same host as BIND9, reference the existing key file. Otherwise, copy it securely to the DHCP host:
cp /etc/bind/ddns-key.solvethenetwork.com.key /etc/dhcp/ddns-key.conf
chown root:root /etc/dhcp/ddns-key.conf
chmod 640 /etc/dhcp/ddns-key.conf
Edit
/etc/dhcp/dhcpd.confto enable DDNS integration:
include "/etc/dhcp/ddns-key.conf";
ddns-updates on;
ddns-update-style interim;
update-static-leases on;
ignore client-updates;
zone solvethenetwork.com. {
primary 10.10.10.1;
key ddns-key.solvethenetwork.com;
}
zone 10.10.10.in-addr.arpa. {
primary 10.10.10.1;
key ddns-key.solvethenetwork.com;
}
subnet 10.10.10.0 netmask 255.255.255.0 {
range 10.10.10.100 10.10.10.200;
option domain-name "solvethenetwork.com";
option domain-name-servers 10.10.10.1;
option routers 10.10.10.254;
ddns-domainname "solvethenetwork.com.";
ddns-rev-domainname "in-addr.arpa.";
default-lease-time 86400;
max-lease-time 172800;
}
With
ddns-update-style interim, the DHCP server registers an A record using the client-supplied hostname (DHCP option 12) and a corresponding PTR for the assigned IP. The
ignore client-updatesdirective prevents clients from attempting their own direct DNS updates — the DHCP server acts as the sole trusted updater. Restart the DHCP service to apply the configuration:
systemctl restart isc-dhcp-server
journalctl -u isc-dhcp-server -f
Logging Dynamic DNS Update Activity
BIND9 categorizes DDNS activity under the
updateand
update-securitylog categories. Directing these to a dedicated log file makes auditing straightforward. Add the following to
/etc/bind/named.conf.logging:
logging {
channel ddns_log {
file "/var/log/named/ddns.log" versions 5 size 10m;
severity info;
print-time yes;
print-severity yes;
print-category yes;
};
category update { ddns_log; };
category update-security { ddns_log; };
};
Create the log directory and set ownership:
mkdir -p /var/log/named
chown bind:bind /var/log/named
Include the logging config in the main
named.conf:
include "/etc/bind/named.conf.logging";
After reloading BIND9, successful dynamic update events appear in
/var/log/named/ddns.log:
26-Mar-2024 14:32:11.452 update: info: client @0x7f3a4c005680 10.10.10.5#52341/key ddns-key.solvethenetwork.com: updating zone 'solvethenetwork.com/IN': adding an RR at 'webserver-01.solvethenetwork.com' A
26-Mar-2024 14:32:11.453 update: info: client @0x7f3a4c005680 10.10.10.5#52341/key ddns-key.solvethenetwork.com: updating zone '10.10.10.in-addr.arpa/IN': adding an RR at '50.10.10.10.in-addr.arpa' PTR
Denied update attempts appear under
update-securityand include the reason for rejection — invaluable for catching misconfigured clients or unauthorized access attempts early.
Zone File Maintenance and Journal Management
Because BIND9 journals dynamic updates incrementally, the on-disk zone file can diverge significantly from the live zone. To merge the journal back into the zone file — required before any manual edits — freeze and thaw the zone using
rndc:
rndc freeze solvethenetwork.com
# The .jnl file is now merged into the zone file on disk
# Make any manual edits here, then increment the serial
rndc thaw solvethenetwork.com
To inspect the current live zone state including all dynamic updates without freezing:
rndc dumpdb -zones
# Output written to the file configured by dump-file in named.conf
# Default: /var/cache/bind/named_dump.db
Warning: Never edit a zone file that has an active
.jnljournal file alongside it while BIND is running. The journal takes precedence over the zone file at startup — manual edits made to the zone file without freezing first will be overwritten when the journal is replayed. Always freeze before editing.
Security Hardening for Production DDNS
Dynamic DNS introduces write access to zone data — a critical attack surface if not hardened correctly. Apply these controls in every production deployment:
- Require TSIG on every dynamic zone. Never use
allow-update { any; };
or IP-only ACLs in production. An attacker on the same network segment could inject or overwrite records without TSIG enforcement. - Prefer update-policy over allow-update. It limits which record types and names a key can modify, containing the blast radius of a compromised credential.
- Issue separate keys per role. The DHCP server, monitoring scripts, and administrative tooling should each hold a distinct TSIG key with only the permissions they need. Revoking one key does not affect others.
- Rotate TSIG keys on a schedule. Generate a new key, deploy it to all consumers simultaneously, verify it works, then remove the old key from
named.conf
and reload. - Protect NS and SOA records. Ensure no update-policy rule permits modification of
NS
orSOA
records. An attacker who can overwrite NS records effectively hijacks your zone delegation. - Alert on update-security log events. TSIG validation failures indicate either misconfigured clients or active probing. Forward these to your SIEM or alerting pipeline.
Troubleshooting Common Issues
nsupdate returns REFUSED
BIND rejected the update. Check that the TSIG key name and secret in the file passed to
-kmatch exactly what is declared in
named.conf. Verify the zone has an
allow-updateor
update-policydirective that grants this key permission. Check
syslogand the
update-securitylog channel for the specific rejection reason.
nsupdate returns NOTAUTH
The target server is not authoritative for the zone specified in the update. Confirm the zone name in the nsupdate script matches the zone declaration in
named.confexactly, including case. Verify BIND has loaded the zone successfully:
rndc zonestatus solvethenetwork.com
BIND fails to start with journal conflict error
If the zone file was edited manually while a journal existed, BIND may refuse to load the zone. Resolve this by stopping BIND, removing the stale journal, and restarting:
systemctl stop bind9
rm /var/lib/bind/db.solvethenetwork.com.jnl
systemctl start bind9
Only remove the journal if the zone file on disk contains the authoritative current state. If the journal contains updates not yet in the zone file and you need to preserve them, use
rndc freezewhile BIND is still running to merge first.
ISC DHCP not registering client hostnames
Check
journalctl -u isc-dhcp-serverfor DDNS update errors. Common causes include: the DHCP client not sending a hostname (DHCP option 12) in its request; a key name mismatch between
dhcpd.confand
named.conf; or the zone name in
dhcpd.confmissing a trailing dot. All zone names in
dhcpd.confDDNS zone blocks must be fully qualified with a trailing period.
Frequently Asked Questions
Q: What is the difference between allow-update and update-policy in BIND9?
A:
allow-updateis a binary gate — any client that passes the check can update any record type in the zone.
update-policyis a rule-based system that lets you specify which key can update which record types at which names. For production use,
update-policyis strongly preferred because it limits the damage a compromised key can cause.
Q: Can nsupdate modify the SOA serial number directly?
A: No. BIND9 automatically increments the SOA serial when a dynamic update modifies the zone. You should not attempt to update the SOA record manually via nsupdate — BIND manages it internally. Trying to do so will typically result in a REFUSED response because most update-policy configurations exclude SOA from permitted record types.
Q: What happens if I delete the .jnl journal file while BIND is running?
A: This is dangerous and should never be done while BIND is active. BIND holds the journal open and continues writing to it. Deleting it from the filesystem while BIND runs can corrupt zone state. Always stop BIND first, or use
rndc freezeto merge and close the journal cleanly before any file-level operations.
Q: How do I add an AAAA record for an IPv6 address using nsupdate?
A: Use the same syntax as for A records but specify the AAAA type and the IPv6 address. For example:
update add host-v6.solvethenetwork.com. 3600 AAAA 2001:db8::a0a:a32. The
update-policyon the zone must include AAAA in the list of permitted record types for the key.
Q: Can multiple DHCP servers update the same BIND9 zone simultaneously?
A: Yes, as long as each DHCP server holds a valid TSIG key that is authorized in the zone's
allow-updateor
update-policy. BIND9 processes updates serially and handles concurrent requests safely. You can issue all DHCP servers the same key, or separate keys with identical permissions — separate keys are preferred so individual servers can be revoked independently.
Q: What TTL should I use for dynamically registered records?
A: Use a low TTL — typically 300 to 600 seconds — for dynamically registered records. Short TTLs ensure that when a DHCP lease expires and the record is deleted or reassigned to a different IP, resolvers and clients pick up the change quickly. Using the zone default TTL (often 3600 seconds) for DDNS records leads to stale cache problems.
Q: Does dynamic DNS work with DNSSEC-signed zones?
A: Yes, but it requires careful configuration. When
auto-dnssec maintainand
inline-signing yesare configured on a DNSSEC zone, BIND9 automatically re-signs dynamic updates as they arrive, maintaining a valid signed zone at all times. Without inline-signing, you would need to manually re-sign the zone after each update, which is impractical for DDNS workloads.
Q: What is the difference between ddns-update-style interim and standard in ISC DHCP?
A: The
standardstyle follows the final RFC 4702/4703 specification exactly and requires both the DHCP client and server to cooperate. The
interimstyle is an older implementation that predates the final RFCs but is widely supported and works reliably with BIND9. For new deployments using ISC DHCP 4.x with BIND9,
interimis the recommended and most compatible style. The
nonestyle disables DDNS entirely.
Q: How do I prevent DHCP clients from overwriting critical static DNS records?
A: Use
update-policyinstead of
allow-update, and craft rules that restrict the DHCP server key to a specific subdomain or hostname pattern. For example, grant the DHCP key access to
wildcard *.clients.solvethenetwork.com.only, keeping all infrastructure hostnames outside that subtree protected. Additionally, the
ignore client-updatesdirective in
dhcpd.confprevents end-user machines from attempting direct DNS updates themselves.
Q: How do I revoke DDNS update access for a compromised key?
A: Remove the
keyblock for the compromised key from
named.conf(or the included key file), remove all references to it in
allow-updateor
update-policydirectives, and run
rndc reconfig. Immediately generate a new key, distribute it to authorized clients, and update their configurations. Review the DDNS update log for any unauthorized changes made while the key was active and correct them.
Q: Can I use nsupdate over TCP instead of UDP?
A: Yes. Pass the
-vflag to
nsupdateto force TCP transport. This is useful for large updates that might exceed the UDP DNS message size limit, or in environments where UDP is filtered on port 53. TCP updates are otherwise functionally identical to UDP updates.
Q: How do I verify the contents of a .jnl journal file?
A: Use the
named-journalprintutility, which ships with BIND9 and prints the journal in human-readable form:
named-journalprint /var/lib/bind/db.solvethenetwork.com.jnl. Each entry shows the transaction ID, the timestamp, and the exact records that were added or deleted. This is useful for auditing what changes have been applied since the last zone file freeze.
