Migrate from Azure Private DNS
Azure Private DNS resolves; DTM resolves and health-gates, load-balances, and fails over. This tutorial moves your zones across without breaking existing resolution: classify what should and should not be imported, import over ARM with a dry run, prove parity while clients stay put, cut one pilot VNet, roll out in tracked waves, and only then decommission the source, in the order that cannot lose writes.
1. Classify every zone: three destinations, not one
Section titled “1. Classify every zone: three destinations, not one”Inventory your Private DNS zones and sort them into three classes, because only one of them should be imported by hand:
- App zones (your
example.internal-style zones): import directly, the rest of this tutorial. privatelink.*zones: never import these by hand. DTM’s Private Endpoint automation owns them (step 6), keeps them current as endpoints churn, and never fights your records.- Auto-registration zones: any zone with VNet links where
registrationEnabledis true. A one-shot import of these goes stale as VMs churn; route them to tag-based VM registration or dynamic DNS instead. Detect them:
az network private-dns link vnet list -g rg-dns -z example.internal \ --query "[?registrationEnabled]"2. Grant the nodes read access to the source
Section titled “2. Grant the nodes read access to the source”The direct import runs over ARM using each node’s managed identity, which
needs Reader (or a custom role granting Microsoft.Network/privateDnsZones
read) on the source zones’ scope:
az role assignment list --assignee <node-principal-id> --scope <rg-id> -o tableThe tell for a missing grant is a 502 Bad Gateway carrying “Azure Private
DNS read failed: … 403 …” from ARM. (A 503 “azure private dns import
is not configured” means the importer is unavailable on that node, not a
permissions problem.) You also need an admin-role DTM token for the import
calls.
3. Dry-run the import
Section titled “3. Dry-run the import”Create the destination zone in DTM, then rehearse (migration documents the endpoint and type mapping):
curl -sk -X POST \ https://10.0.250.4:8443/api/v1/zones/<zone-id>/import-azure-privatedns \ -H "Authorization: Bearer $DTM_TOKEN" -H "X-DTM-CSRF: 1" \ -H "Content-Type: application/json" \ -d '{ "subscription_id": "00000000-0000-0000-0000-000000000000", "resource_group": "rg-dns", "private_zone_name": "example.internal", "dry_run": true }'# review the created / updated / unchanged / conflicts counts before committing4. Import, then re-run for the drift baseline
Section titled “4. Import, then re-run for the drift baseline”Commit with dry_run: false, then run the same import again and confirm
everything reports unchanged. The import is idempotent by design, and that
all-unchanged second run is your drift baseline: from now on, re-running it
is a cheap sweep that shows exactly what changed on the Azure side.
5. Parallel-run and sample parity
Section titled “5. Parallel-run and sample parity”Clients stay on Azure-provided DNS while you prove the copy. From a VM inside a linked VNet (Azure’s resolver is reachable only from inside), compare answers per name and type:
for name in app db cache; do echo "$name: DTM=$(dig @10.0.250.4 $name.example.internal +short) \Azure=$(dig @168.63.129.16 $name.example.internal +short)"doneExpect SOA and NS to differ by design: the import skips them, and
DTM manages its own. Everything else should match exactly; a mismatch means
a write landed after your import, which the drift sweep from step 4 will
show.
6. Hand the privatelink zones to the automation
Section titled “6. Hand the privatelink zones to the automation”curl -sk -X PUT https://10.0.250.4:8443/api/v1/config/private-endpoints \ -H "Authorization: Bearer $DTM_TOKEN" -H "X-DTM-CSRF: 1" \ -H "Content-Type: application/json" -d '{"enabled": true}'
curl -sk -X POST https://10.0.250.4:8443/api/v1/discovery/private-endpoints/run \ -H "Authorization: Bearer $DTM_TOKEN" -H "X-DTM-CSRF: 1"The run-now call saves waiting out the interval. Two facts to rely on: only
the allowlisted official Microsoft privatelink.* zones are managed (the
allowlist is not extensible), and the automation never overwrites an
operator-created record at the same name. Prerequisite: Reader over the
discovery scopes; details on
Private Endpoint DNS automation.
Verify: a known private endpoint name resolves from a DTM node.
7. Add what Private DNS could not do
Section titled “7. Add what Private DNS could not do”Pick the records that matter and enrich them: a health-check template and a load-balancing policy per record (health checks, methods). Prove one by stopping a backend and watching the answer flip on the probe-math schedule. This is the payoff step; a migration that ends with identical behavior was just a move.
8. Cut over one pilot VNet
Section titled “8. Cut over one pilot VNet”az network vnet update -g rg-pilot -n vnet-pilot --dns-servers 10.0.250.4 10.0.250.5Then verify from a VM inside it (internal names, private endpoint names, and internet recursion).
9. Roll out in waves, tracked by policy
Section titled “9. Roll out in waves, tracked by policy”Cut remaining VNets over in waves, and tag each one dtm-dns=enforced as
part of its wave: the shipped
DNS-adoption policy evaluates only VNets
carrying that tag, so the compliance blade doubles as your rollout tracker,
and its effects are Audit or Disabled only, never Deny. One standing chore:
the policy assignment’s dtmDnsServerIps parameter lists your node IPs, so
re-set it after any add-node or
remove-node that changes them.
10. Decommission, freeze first
Section titled “10. Decommission, freeze first”The ordering here is what protects the last writes:
- Freeze all writes to the Azure Private DNS zones (announce it; remove write access if your process allows).
- Run one final import sweep and confirm the all-unchanged counts. That is the completion proof: nothing landed since your last sync.
- Hold a read-only rollback window (the zones still exist, unlinked writes frozen) for as long as your change process wants.
- Remove the VNet links, then delete the Azure zones.
Freezing after a final sweep, the tempting order, silently loses any write that lands between the sweep and the link removal.
Where next
Section titled “Where next”- Adopt DTM one name at a time: the lower-commitment ramp, if a full zone migration is more than today needs.
- Production readiness pass: harden before the waves widen.
- Migrating to Hayami DTM: import mechanics for every source format.
Need a hand? Email [email protected].
Last validated: 2026-07-26