TL;DR: Use wildcard DNS only for shipment-tracking subdomains beneath a zone the logistics platform controls. For a hostname in a customer's zone, use tenant-specific proof and routing records. The practical default is a hybrid: fast wildcard issuance for platform names, exact per-tenant evidence for customer names, and four timestamps that show what the verifier observed.
| Model | Pick it when | Cutover effect | Audit evidence | Limit |
|---|---|---|---|---|
| Wildcard record | The platform owns the parent zone | New matching names need no individual DNS publication | Zone change and exact-host request logs | Cannot prove control of a customer's zone |
| Per-tenant CNAME | The customer controls the hostname | Each customer moves independently | Returned target, observer, and time | Adds lifecycle state |
| TXT proof plus CNAME | Proof and routing must be separate | Proof can finish before traffic moves | Token and route observations stay distinct | May require two DNS changes |
| Hybrid | Both hostname types are offered | Fast defaults and deliberate custom cutovers | Evidence follows zone ownership | Two paths share one state machine |
A tracking URL printed on a label can outlive its onboarding screen. A green verified badge is therefore weak evidence unless an operator can see what was queried, what came back, where it was observed, and which transition followed.
Which DNS model matches the name's real owner?
A wildcard such as *.tracking.example.net belongs to the operator of example.net. RFC 4592 defines wildcard synthesis using DNS tree rules: a wildcard may supply an answer when the queried name does not otherwise exist. That is useful for a platform-issued name such as north-yard.tracking.example.net.
It says nothing about control of status.customer.example. The customer controls that zone. Bind verification to the requested name or a documented validation name beneath it. A random TXT value shows that someone able to change the relevant DNS data completed the challenge. A CNAME answer shows where traffic is directed. These observations are related, but they are not interchangeable.
Keep application authorization separate too. Deleting a tenant binding must stop routing even while old DNS data remains cached. A stale answer must never recreate a deleted binding. DNS supplies evidence and routing data; the host registry decides whether a request is allowed.
For names the platform issues, choose a wildcard when the platform controls the parent zone, generated hostnames share an ingress class, and every request is checked against an exact-host registry. The wildcard removes a publication step. It does not remove authorization.
Here is the path in words. A resolver asks for a generated hostname. Authoritative DNS synthesizes an answer. Ingress reads the Host value. The registry maps that normalized value to one tenant, and unknown hosts fail closed.
Test the boundary. An explicit DNS name can prevent wildcard synthesis at that name, while a delegated child has its own authority. Wildcards follow the DNS tree, not a string-suffix shortcut. Test explicit names, nonexistent names, and delegated children before rollout.
Pick tenant records for customer-controlled zones
Per-tenant records cost more operational state, yet that state answers the question support actually gets: is proof absent, is routing stale, or is the destination not ready? One boolean cannot distinguish those cases.
Store four signals:
-
proofObservedAt, when the expected TXT value appeared. -
routeObservedAt, when the expected CNAME target appeared. -
readyAt, when the exact host was registered and ready to serve. -
cutoverConfirmedAt, when an external check reached the intended host after readiness.
Order matters. Prepare the destination before asking a customer to move a label-facing hostname. Verification can run ahead of cutover; confirmation comes last. Fast cutover comes from parallel preparation, not an assumed universal propagation time.
RFC 1035 defines TTL as a resource-record field expressed in seconds. It affects cache lifetime, but it does not promise that every observer changes at one wall-clock instant. Record the expected authoritative data and each resolver observation separately. Add jitter to idempotent retries, and choose the polling window from the workflow's tolerance rather than inventing a global deadline.
Observe the cutover in Node.js
This core checks proof and routing independently. The DNS reader is injected so tests can control every answer and production can name its observation vantage.
import { randomUUID } from "node:crypto";
import { Resolver } from "node:dns/promises";
type DnsReader = Pick<Resolver, "resolveTxt" | "resolveCname">;
type Result = {
checkId: string;
observedAt: string;
proof: { matched: boolean; answers: string[] };
route: { matched: boolean; answers: string[] };
};
const normalize = (value: string): string =>
value.trim().toLowerCase().replace(/\.$/, "");
export async function observeDomain(
dns: DnsReader,
hostname: string,
verificationName: string,
expectedToken: string,
expectedTarget: string,
): Promise<Result> {
const [txt, cname] = await Promise.allSettled([
dns.resolveTxt(verificationName),
dns.resolveCname(hostname),
]);
const txtAnswers = txt.status === "fulfilled"
? txt.value.map((chunks) => chunks.join(""))
: [];
const cnameAnswers = cname.status === "fulfilled"
? cname.value.map(normalize)
: [];
return {
checkId: randomUUID(),
observedAt: new Date().toISOString(),
proof: {
matched: txtAnswers.includes(expectedToken),
answers: txtAnswers,
},
route: {
matched: cnameAnswers.includes(normalize(expectedTarget)),
answers: cnameAnswers,
},
};
}
Do not stop at this return value. Production telemetry should retain a bounded failure category such as timeout, no data, or server failure. Log the check ID, tenant ID, normalized hostname, check kind, outcome, resolver vantage, duration, and state transition. Keep proof tokens out of routine logs, and avoid raw exception strings as metric labels because they create unbounded cardinality.
Count checks by outcome and kind. Measure time between requested, proof observed, route observed, ready, and confirmed. Alert on a sustained change in failure ratio or on tenants stuck after readyAt, not on one missed lookup.
One sample is a clue.
Inject a fake DnsReader in tests. Cover TXT-only success, CNAME-only success, both matches, missing data, trailing dots, mixed case, and rejected lookups. External checks from several resolver vantages can reveal cache differences. Store each result separately; combining them into a fictional unanimous answer destroys useful evidence.
Cache disagreement is normal.
Limits and decision rule
DNS verification demonstrates control of a DNS change at an observation time. It does not prove organizational identity, permanent control, or continuing authorization after a tenant is disabled. Recheck under an explicit policy, while making disablement effective in the application without waiting for caches.
This trade-off has a hard limitation. Wildcard routing is not appropriate for customer-controlled names because it cannot attribute a change in the customer's zone. Per-tenant records are a poor fit for disposable platform-issued names when the only result is repetitive zone data and a slower onboarding path. The hybrid is also not free: teams must test two provisioning paths and keep their state vocabulary identical. Choose the simpler single model when the product exposes only one kind of hostname.
CNAME records do not solve email authentication either. SPF, DKIM, and DMARC are a separate workflow; RFC 7489 specifies DMARC policy and reporting based on authenticated identifiers. Do not hide mail readiness inside a web-hostname verified flag.
Use wildcards to optimize issuance inside your zone. Use tenant-specific proof and routing records to optimize attribution inside the customer's zone. The four timestamps make both paths explainable during a shipment-status cutover, without claiming DNS converges on a schedule it does not guarantee.
Top comments (0)