Short answer: retire only the records owned by the departing property, never the whole DNS zone, unless an ownership check proves that the zone itself has no other consumers. Lower the relevant TTL before the cutover, wait for the old TTL window to pass, delete by an exact inventory, and keep restoration data until resolver checks agree. The deciding constraint is propagation delay versus cutover speed: a fast API response says that authoritative state changed, not that every cache has forgotten the previous answer.
This is the rule I would want in the runbook when a property manager clicks "Offboard" at 3 a.m. A shared zone can serve leasing pages, resident portals, mail authentication, and maintenance integrations for several buildings. Deleting that zone because one building left turns a routine tenant lifecycle event into an incident with a wide, poorly bounded blast radius.
The page that fires should name failed record checks and affected properties. A red DNS dashboard is not enough.
Should domain offboarding delete records or remove the whole zone?
DNS has two relevant clocks. The control plane accepts a mutation on its own schedule; recursive resolvers retain prior answers according to TTL. During that interval, some clients can observe the old destination while others observe the new state. Treating the mutation response as completion collapses those clocks and makes an intermittent result look mysterious.
For a property portfolio, the operational unit should be a record set tied to a property identifier, not a zone inferred from a domain suffix. Consider oak.example.net: its leasing site may be leaving, while _dmarc.example.net and mail-related names still protect or route traffic for the portfolio. DMARC policy is published in DNS and can apply at an organizational-domain boundary, so deleting a parent-zone policy during one property's exit can affect mail handling beyond the departing site.
The dangerous shortcut is broad scope. A request such as "remove everything under this zone" has no evidence that the caller owns everything it reaches. An exact inventory does: name, type, value, TTL, property ID, and lifecycle state can be reviewed before any mutation occurs.
Scope first.
Make the offboarding contract record-scoped
The admin console should create a plan before it creates side effects. That plan is immutable, carries a unique operation ID, and contains the expected current value for every record. The mutation worker then uses compare-and-delete semantics: if live state differs from the plan, it stops instead of deleting a record that another workflow has changed.
Use a small state machine: active, ttl_lowering, ready, deleting, verifying, then retired. A transition into ready requires evidence that the previous, longer TTL has had time to expire. The waiting interval is derived from the old TTL recorded in the plan, not from the newly lowered TTL; changing a TTL does not retroactively shorten answers already cached.
The zone itself needs a separate ownership class. A dedicated zone may be eligible for removal only when its inventory is empty apart from explicitly approved zone-level records, no property or service references it, and a second authorization approves the larger blast radius. In a shared zone, zone deletion is denied by policy. No checkbox should bypass that distinction.
| Decision | Required evidence | Default action |
|---|---|---|
| Retire one property | Exact record inventory and expected values | Delete listed record sets |
| Live value changed | Planned value differs from authoritative value | Stop and re-plan |
| Shared zone detected | More than one owner or service reference | Deny zone deletion |
| Dedicated zone appears empty | No remaining consumers and separate approval | Schedule zone removal |
| Verification is mixed | Authoritative and recursive observations disagree | Keep rollback open |
Fast is useful only after scope is safe. For a scheduled cutover, lowering TTL ahead of time narrows the later cache window, but it also increases query frequency while the lower value is active. For an unscheduled offboarding, there is no honest way to erase previously cached data immediately; the runbook should expose that delay rather than promise instant convergence.
Take a concrete planned change where the old TTL is 3,600 seconds and the desired cutover TTL is 300 seconds. The operator lowers the value to 300, but the workflow cannot become ready merely 300 seconds later, because a resolver that fetched the earlier answer just before the change may still retain it for nearly the full 3,600-second window. After that old window passes, deleting the leasing record leaves a shorter period in which newly cached pre-delete answers can remain. The trade-off is explicit: lower TTLs can improve cutover responsiveness, while higher TTLs reduce how often caches need fresh answers. Neither setting makes a zone-wide delete safe, and neither guarantees what an unobserved resolver currently holds.
Encode guardrails in the worker
The following Go sketch keeps the important boundary visible. It accepts a previously reviewed plan, rejects zone deletion for shared ownership, checks the current record against the expected record, and writes an audit event around each mutation. The interfaces are deliberately generic because these invariants belong in the application, independent of the DNS control plane behind it.
package offboard
import (
"context"
"errors"
"fmt"
)
type Record struct {
Name string
Type string
Value string
TTL uint32
}
type Plan struct {
OperationID string
PropertyID string
SharedZone bool
Records []Record
}
type DNS interface {
LookupAuthoritative(context.Context, string, string) (Record, error)
DeleteRecord(context.Context, Record) error
}
type Audit interface {
Write(context.Context, string, string, Record) error
}
func Apply(ctx context.Context, dns DNS, audit Audit, plan Plan) error {
if plan.OperationID == "" || plan.PropertyID == "" {
return errors.New("plan identity is required")
}
if !plan.SharedZone {
return errors.New("dedicated-zone retirement requires a separate workflow")
}
for _, expected := range plan.Records {
current, err := dns.LookupAuthoritative(ctx, expected.Name, expected.Type)
if err != nil {
return fmt.Errorf("authoritative lookup %s %s: %w", expected.Name, expected.Type, err)
}
if current != expected {
return fmt.Errorf("record changed after planning: %s %s", expected.Name, expected.Type)
}
if err := audit.Write(ctx, plan.OperationID, "delete-started", expected); err != nil {
return fmt.Errorf("write audit event: %w", err)
}
if err := dns.DeleteRecord(ctx, expected); err != nil {
return fmt.Errorf("delete %s %s: %w", expected.Name, expected.Type, err)
}
if err := audit.Write(ctx, plan.OperationID, "delete-accepted", expected); err != nil {
return fmt.Errorf("write audit event: %w", err)
}
}
return nil
}
Idempotency matters here. A retry with the same operation ID should continue verification when a record is already absent, while a new operation must not silently inherit that result. The compact sketch returns lookup errors so the distinction remains explicit; a production adapter should represent present, absent, and lookup failed as different outcomes rather than interpreting every error as absence.
There is a limitation: compare-and-delete protects only records represented accurately in the inventory. It is not suitable for a poorly cataloged zone where ownership cannot be established, and automation should stop in that case. A dedicated-zone workflow is the better fit only after dependency discovery proves that the zone is isolated; until then, manual investigation is slower but has a defensible blast radius.
No guesswork.
Audit writes also need a defined failure policy. If the pre-mutation audit cannot be persisted, do not mutate. If the post-mutation audit fails after the DNS change succeeded, preserve the operation in a recoverable queue and page on the audit gap; blindly retrying the delete without reconciling authoritative state creates ambiguity. This is exactly where a cheerful dashboard tends to hide the question that matters: which page fired, for which record, and can the operator prove what happened?
Verify convergence, not button clicks
Verification begins at the authoritative source. Each planned record should either be absent or have the intended replacement value, and every unplanned record in the zone should remain unchanged. Then query a defined set of recursive resolvers from the networks that matter to the property workflow. Resolver diversity is a sampling strategy, not proof that every cache on the Internet has converged.
Keep the checks concrete. For the leasing hostname, test the exact name and record type. For mail offboarding, separately inspect the names involved in mail routing and authentication; do not infer their state from the website result. DMARC deserves explicit attention because its policy and reporting addresses are DNS-published controls, and organizational-domain policy can cover subdomains.
Silence is not success.
Alert on a stuck lifecycle state, authoritative values that disagree with the plan, recursive observations that remain mixed after the planned TTL window, deletion attempts outside an approved inventory, and any request to delete a shared zone. The alert payload should include the operation ID, property ID, record name and type, expected state, observed state, old TTL, elapsed time, and the last successful transition. Those fields let an on-call engineer decide whether to wait, stop, or restore without reconstructing the change from screenshots.
Roll back while the evidence is still intact
Before deletion, store a restoration manifest containing every exact record and its prior TTL. Protect it with the same access controls and retention policy as other operational change data. Rollback means recreating only records deleted by that operation, after checking that no replacement now occupies the same name and type; it does not mean restoring the entire zone from an old snapshot and overwriting unrelated changes.
Trigger restoration when the offboarding target was wrong, a required dependency was omitted from the plan, or verification shows that service must return. Do not roll back merely because one recursive resolver still holds an old answer inside the expected cache interval. That observation calls for waiting and continued measurement, not another mutation.
The final closeout record should preserve the approved plan, mutation results, verification observations, and rollback disposition. The practical decision rule is plain: record-level deletion is the normal path for shared property domains; whole-zone removal is a separately authorized lifecycle event for a demonstrably dedicated and unused zone. Cutover speed can be tuned with TTL planning. Scope cannot be repaired after a broad delete has escaped.
References
- RFC 7489, Domain-based Message Authentication, Reporting, and Conformance (DMARC): https://datatracker.ietf.org/doc/html/rfc7489
Top comments (0)