Short answer: move the school-facing hostname to its replacement first, retain the old target for a rollback window, and remove only the records whose zone and tenant ownership you have verified. If the school owns the parent zone, give its administrator the exact record changes; your application cannot safely delete records in a zone it does not control. A dedicated delegated zone makes retirement a smaller operation, but delegation itself still belongs to the parent-zone owner.
For a learning app moving learn.school.example between runtime endpoints, the path is straightforward: a request reaches the hostname, a DNS answer directs it to an endpoint, and that endpoint must present the right certificate and recognize the hostname. Record deletion comes last. A DNS answer changing does not instantly drain cached answers or existing connections, so keep both old and new serving paths available while the cutover is observed.
Delete last.
How can you offboard a custom domain without touching other tenants' records?
Keep a written mapping of hostname, current answer, replacement answer, zone ID, tenant ID, and record ID. Record names alone are poor deletion keys: two schools can use similar labels in different zones, and a provider's account-wide search can return both. The cutover should change the exact record at the exact zone, with an explicit expected old value. If the observed value has changed since the inventory was taken, stop and investigate; another operator may have made a legitimate change.
The example below models the offboarding decision without assuming a particular DNS service. It produces an action for a record inside a platform-managed zone, or instructions for the administrator of a customer-owned zone. It never treats possession of a hostname as permission to edit its DNS. The sample domains and endpoints are documentation examples, not live infrastructure.
type RecordRef = {
id: string;
zoneId: string;
tenantId: string;
name: string;
type: "CNAME";
value: string;
};
type Cutover = {
tenantId: string;
hostname: string;
zoneId: string;
zoneOwner: "customer" | "platform";
recordId: string;
oldTarget: string;
newTarget: string;
};
function planCutover(change: Cutover, record: RecordRef) {
if (
record.id !== change.recordId ||
record.zoneId !== change.zoneId ||
record.tenantId !== change.tenantId ||
record.name !== change.hostname ||
record.type !== "CNAME" ||
record.value !== change.oldTarget
) {
throw new Error("Inventory changed; do not update DNS");
}
const instruction = {
zoneId: change.zoneId,
recordId: change.recordId,
hostname: change.hostname,
from: change.oldTarget,
to: change.newTarget,
rollbackTo: change.oldTarget,
};
return change.zoneOwner === "customer"
? { action: "request-owner-change" as const, instruction }
: { action: "apply-zone-scoped-change" as const, instruction };
}
const change: Cutover = {
tenantId: "school-42",
hostname: "learn.school.example",
zoneId: "school-example-zone",
zoneOwner: "customer",
recordId: "learn-cname-7",
oldTarget: "runtime-old.example.net",
newTarget: "runtime-new.example.net",
};
const record: RecordRef = {
id: "learn-cname-7",
zoneId: "school-example-zone",
tenantId: "school-42",
name: "learn.school.example",
type: "CNAME",
value: "runtime-old.example.net",
};
console.log(planCutover(change, record));
This is a planner, not an atomic DNS update. An actual adapter should read the record again immediately before writing and use a provider-supported conditional update when available. If conditional writes are unavailable, serialize changes for that record in your own control plane and verify the result afterward; a second read alone cannot eliminate a race. Log the zone ID, tenant ID, record ID, observed value, intended value, and operation outcome, but avoid treating a successful write response as proof that every resolver now sees the new answer.
Where does zone ownership change the procedure?
For a customer-owned parent zone, the school controls the authoritative record. Supply the exact hostname, record type, previous target, new target, and rollback value. Confirm the replacement serves that hostname before requesting the change. Later, ask the same owner to remove the old record only when the hostname itself is being retired. Do not infer control of school.example from control of a runtime target under example.net.
For a platform-owned zone, keep tenant and zone identifiers attached to every mutation. A delegated subdomain can isolate a school's records operationally, but removing the child zone before the parent delegation is updated creates a different failure: resolvers can still follow the parent's NS referral toward a zone that no longer answers as intended. Plan parent delegation changes with its owner, then retire the child zone after checks and the agreed rollback window. DNS zone cuts are real boundaries, not string prefixes; RFC 1034 describes how delegation works.
The distinction matters for cost as well as safety. A dedicated zone adds provisioning and monitoring work per school; shared zones reduce that overhead but demand stricter record-level authorization. This is a real trade-off: dedicated zones are a poor fit if the school cannot maintain a delegation or your team cannot monitor thousands of child zones, while shared zones increase the consequences of a mistaken broad deletion. Choose based on who can approve and reverse a change, not a claimed per-record saving. Never use a broad delete by zone operation merely because the school has left: the zone may still serve another tenant's hostnames, mail, or verification records. In particular, DMARC policy is published as DNS TXT data under _dmarc and should not be swept up with an application-hostname cleanup. One more boundary is easy to miss: a customer can change its parent-zone record after your inventory snapshot, and no record ID in your database can stop that. Your procedure needs a fresh owner confirmation when you don't control the zone.
How do you verify the cutover before deletion?
Check authoritative answers and the behavior of the HTTPS endpoint separately. First verify that the intended authoritative zone contains the expected answer and that the new endpoint serves the hostname with a valid certificate and the correct tenant routing. Then inspect answers from recursive resolvers over the transition window. Cached answers can persist until their TTL expires, and negative answers have their own caching behavior; neither a single local lookup nor a successful control-plane update proves global convergence (RFC 1035 and RFC 2308).
Keep the old endpoint capable of serving this school until the agreed observation window ends. Rollback should restore the recorded old DNS target and confirm that the old endpoint still recognizes the hostname. It is not instant: resolvers that cached the new answer can continue using it until that answer expires. Watch both targets for hostname-specific request failures and certificate errors. An aggregate healthy status can conceal a broken school hostname.
Deletion is a separate, narrower decision. When the school has confirmed that the old hostname is no longer needed, fetch the inventory again, compare the record's zone, tenant, type, name, and value, and remove only that record through the authorized zone owner. If the hostname will remain active at the replacement, delete the obsolete runtime binding instead of the DNS record. Leave unrelated TXT, MX, NS, and other tenant records untouched. A failed comparison is a stop signal, not a reason to widen the delete query.
The limit is explicit: this plan protects records only when the inventory accurately identifies their owner and the person applying the change has the right zone permissions. It can't make an uncoordinated customer-zone edit atomic with an application deployment. Where that coordination isn't available, postpone deletion and keep the old binding until ownership can be confirmed.
Before closing the offboarding ticket, retain the previous answer and change approval, confirm the replacement and rollback paths were both tested, inspect authoritative and recursive results, and record which party owns the final DNS removal. Then revoke the old endpoint's hostname binding only after its rollback window has closed. That order costs a little temporary overlap, but it buys an actual way back without taking another school's records along for the ride.
Further reading
- RFC 1034, Domain names: concepts and facilities: https://datatracker.ietf.org/doc/html/rfc1034
- RFC 1035, Domain names: implementation and specification: https://datatracker.ietf.org/doc/html/rfc1035
- RFC 2308, Negative Caching of DNS Queries: https://datatracker.ietf.org/doc/html/rfc2308
- RFC 7489, Domain-based Message Authentication, Reporting, and Conformance: https://datatracker.ietf.org/doc/html/rfc7489
Top comments (0)