Skip to content

Adopt DHCP self-registration safely

The self-inflicted outage in every DHCP-DNS story is scavenging configured shorter than the lease renewal cycle: healthy machines’ records quietly deleted by the cleanup meant for dead ones. This tutorial does the adoption in the order that cannot produce that outage: seed first so nothing goes dark, cut DHCP over and prove registration, and only then enable scavenging with a window derived from the lease time, plus the verification drill that proves it spares the living.

Create dyn.corp.example.internal. and the matching reverse zone (1.0.10.in-addr.arpa. for 10.0.1.0/24). One expectation to set correctly now: DTM’s automatic PTR maintenance (maintain_reverse) applies to records written through the API, UI, and Terraform, not to RFC 2136 updates. DHCP-registered PTRs come from the DHCP server updating the reverse zone itself, which every standard DDNS implementation does; the reverse zone just has to exist and accept its key.

Export the current zone from Windows DNS (Export-DnsServerZone) and import it, so every existing hostname resolves from DTM throughout the transition. Two facts about what you just created:

  • Imported records are static: the scavenger never touches them, so the seed cannot be eaten no matter what you configure later.
  • A seeded static record converts to dynamic only when the DHCP server deletes and re-creates it; an in-place renewal of the same value leaves it static. Whether that happens depends on your DHCP server’s update style (many, including Kea by default, do not re-send updates on an unchanged renewal), so parts of the estate may stay static indefinitely. That is safe, just worth knowing for step 7.

Follow dynamic DNS registration: create a TSIG key and bind it to both zones (forward and reverse), then enable updates with require_tsig: true (these two switches are the runtime API, no restart). The danger aside from that page applies verbatim: never run with unsigned updates accepted.

Configure Kea (or dhcpd) DDNS with the TSIG key, the DTM node IPs as the update targets, and both forward and reverse updates on. Verify one lease end to end before touching the estate:

Terminal window
# from a test client: renew the lease, then
dig @10.0.250.4 testhost.dyn.corp.example.internal +short # forward
dig @10.0.250.4 -x 10.0.1.57 +short # PTR, written by DHCP
curl -sk "https://10.0.250.4:8443/api/v1/audit?resource=record&limit=10" \
-H "Authorization: Bearer $DTM_TOKEN" # attributed to the key

5. Size the scavenging window from the lease

Section titled “5. Size the scavenging window from the lease”

Now, and only now, the cleanup. The rule: scavenge_max_age comfortably above twice the lease time. Eight-hour leases renew at four hours, so records refresh at least every eight; 86400 (24 hours) gives a 3x margin and still clears a decommissioned machine within a day:

dynamic_dns:
scavenge_max_age: 86400 # seconds; 0 disables
scavenge_interval: 3600

These two keys are file-or-environment only, with no runtime API; the rolling restart in step 6 is what applies them (configuration reference). The enable and TSIG switches you already set via the API stay where they are.

6. Apply with a rolling restart, knowing what the first restart means

Section titled “6. Apply with a rolling restart, knowing what the first restart means”

Edit the file on every node, then restart one node at a time gated on /readyz returning 200.

7. Watch one full cycle: the living survive, the leftovers surface

Section titled “7. Watch one full cycle: the living survive, the leftovers surface”

Over the next lease cycle plus your max age:

  • Actively renewing hosts survive: every renewal, even an identical-address one, refreshes the record’s age. Verify a known-active host is still present after the max-age window has elapsed; that dig is the proof the sizing is right.
  • Static leftovers are candidates, not corpses: seeded records are static and never scavenged, and because a same-value renewal does not convert a record to dynamic, a "dynamic": false record after a full lease cycle may belong to a decommissioned machine or to a live host whose DHCP server never re-created the record. List the static survivors, then verify each before deleting: no active lease on the DHCP server and no query traffic for the name means it is safe to remove.
Terminal window
curl -sk https://10.0.250.4:8443/api/v1/zones/<zone-id>/records \
-H "Authorization: Bearer $DTM_TOKEN" | jq '[.[] | select((.dynamic|not) and (.system|not)) | .name]'
# cross-check each against the DHCP lease table before deleting (the extra
# .system filter drops DTM's read-only synthesized NS records, which are
# not candidates)

That list is your inherited-cruft shortlist, made visible instead of guessed at; the lease table is the judge.

8. Cut resolvers over and retire the old target

Section titled “8. Cut resolvers over and retire the old target”

Point the office VNet or DHCP option 006 at the DTM nodes, verify a client resolves both directions, then remove the DDNS target and the zone from Windows DNS once its query logs go quiet.

Rollback lever, should you ever need it: setting scavenge_max_age: 0 and rolling the restart disables scavenging entirely without touching a single record.

Need a hand? Email [email protected].

Last validated: 2026-07-27