Skip to content

Release notes

Each Hayami DTM release on the Azure Marketplace, newest first. Before you upgrade, read the breaking changes and upgrade notes of every version between the one you run and the one you are moving to, then upgrade one node at a time as Rolling upgrades describes.

This release includes security improvements; we recommend all customers upgrade.

  • The DNS query log ships through the Logs Ingestion API, and the Data Collector settings are removed. Microsoft ended support for the HTTP Data Collector API on 2026-09-14, and it does not work with Azure Monitor Private Link Scope. The query log now goes to a data collection rule, authenticated by the node’s managed identity, with no shared key. Set azure.logs_ingestion_endpoint, azure.logs_ingestion_dcr_immutable_id and azure.logs_ingestion_stream (default Custom-DTMLogs) in the configuration file, in PUT /api/v1/config/azure-monitor, in Terraform’s dtm_azure_monitor_config or on the Settings page, and give each node’s managed identity Monitoring Metrics Publisher on the rule. The DTMLogs_CL table and its columns are unchanged, so the shipped workbook, the KQL alert pack and your own queries keep working. What is removed:

    • azure.log_analytics_workspace_id and azure.log_analytics_workspace_key, their API fields, and the dtm_azure_monitor_config attributes log_analytics_workspace_id, log_analytics_workspace_key_set and log_analytics_workspace_key_wo (with its version);
    • the Terraform monitoring module’s workspace_key output.

    A configuration file that still carries them loads, and start-up logs an error that names the replacement; nothing ships through them and DNS keeps serving. A PUT that still sends them answers 400, naming the replacement. See the upgrade notes for the one-time table migration and the key rotation.

  • The Terraform and Bicep kits can create the query log’s Logs Ingestion setup, off by default, so a deployment that does not opt in plans exactly as before. In the Terraform monitoring module, set logs_ingestion_enabled = true and pass the nodes’ identities in node_principals: it creates the DTMLogs_CL table, a data collection endpoint, a data collection rule and one role assignment per identity, scoped to the rule, and outputs the three values dtm_azure_monitor_config takes. In the single-region and dual-region Bicep plans, set enableLogsIngestion with enableObservability. The Marketplace (ARM) templates do not create it: the Azure Monitor page of the docs shows how to create the setup by hand for a Marketplace deployment, including a no-shell restart and what a node does when the rule cannot be reached.
  • A failed stable-IP swap that leaves the stable IP unserved now raises an alert. The new gauge dtm_lifecycle_swap_stable_ip_unrestored counts the stable IPs whose latest swap run failed without the address back on a running VM, and the new critical alert DTMSwapStableIPUnrestored (also in the Azure Monitor alerting samples) fires on it. A failed run reports stable_ip_outcome (not_restored or not_confirmed) in GET /api/v1/lifecycle/swap. The gauge reports what the swap recorded, not a live check of the address: after you fix the address by hand, retry the swap to clear it.
  • New metrics for the query log: dtm_observability_querylog_retimed_rows_total counts rows whose own time Azure replaced because the node’s clock is more than two days behind or a day ahead (a sign the clock is wrong), and dtm_observability_log_api{api} says what the node ships through (logs_ingestion or none).
  • Built with Go 1.26.10, which distrusts eight compromised eMudhra (emSign) intermediate certificates. Earlier releases accepted a public certificate chain that passes through one of them. See the upgrade notes for nodes that are still on 1.0.5.
  • The Azure Monitor metrics target is validated. PUT /api/v1/config/azure-monitor answers 400, naming the field, for a monitor_region that is not lower-case letters and digits or a monitor_resource_id that is not an Azure resource ID. A node logs an error at start-up for the same values in the configuration file, and the exporter no longer follows a redirect. A valid region and resource ID are unchanged.
  • The query log is sent in batches, where the old path made one request per DNS query (see query-log shipping). A throttled or briefly unavailable endpoint is retried. The TimeGenerated of a row is now the query’s own time, not the ingestion time. dtm_observability_push_total{exporter="azure_log"} now counts one outcome per batch, where it counted one per row.
  • The Azure Monitor endpoint must be an Azure Monitor host. The node sends its managed identity token to it, so an IP address or a host outside .monitor.azure.com is refused.
  • Script health checks can be changed or deleted only by an admin. A non-admin PUT or DELETE on a protocol: script check answers 403 and leaves the check as it was. Editors keep full control of every other check. A background process that a script check leaves behind is now stopped with it. See HAY-2026-003.
  • Security fix: admin-only API endpoints now refuse every account below admin. Earlier versions could, in rare cases, let a readonly or editor account through. Upgrade to 1.0.6 (HAY-2026-002).
  • GET /api/v1/config/backup is admin only. It names the storage container that receives snapshots, the local directory and whether snapshots are encrypted. A readonly user or a service account that scripted this call now gets 403.
  • GET /api/v1/lifecycle/swap and GET /api/v1/lifecycle/swap/{run_id} are admin only, and the dtm-ui Lifecycle page shows a non-admin an empty swap panel. GET /api/v1/lifecycle/oracle stays readable by every role, but the text of its last_error is shown to admins only; other roles read hidden (admin only).
  • A wildcard record in a Microsoft privatelink zone is refused, such as * in privatelink.blob.core.windows.net, on the API, every zone importer and dynamic updates, with a message that says to create one record per Private Endpoint. DTM never answers from such a record, leaves it out of zone transfers and writes it as a comment in a BIND export. One already stored, for example after an upgrade, is named in a warning at every start-up and listed by GET /api/v1/diagnose under storage.api_refused_records with the rule privatelink_wildcard. Nothing removes it for you. A wildcard in any other zone is unaffected. See HAY-2026-004.
  • The BIND zone-file export writes a record it could not have loaded as a ; comment, with the reason, instead of as a record. That is a record whose name is not a valid DNS name, whose type is not supported, or whose value (or CAA tag) fails the check the API applies. A stored value that fails its type’s rule, such as an A record that is not an IPv4 address, is now a comment too; a BIND server would not have loaded that line either. Valid records export as before.
  • Health-check targets are screened for cloud metadata and platform addresses (and, when a target is saved, loopback addresses) written in IPv6 forms that embed an IPv4 address, as they already were when written as IPv4. The NAT64 local-use prefix 64:ff9b:1::/48 is refused outright. Azure virtual networks do not provide these paths by default.
  • Ignore any update-appliance setting; it has no effect in 1.0.6. Nothing in your deployment needs to change for that.
  • A forwarded answer served from the cache counts its TTL down. A client that asked near the end of an entry’s life used to be handed the original TTL, and could keep the answer for up to about twice what the upstream intended. Each record’s TTL is now reduced by the seconds since the entry was stored, to a floor of 1 (a TTL the upstream sent as 0 stays 0), and a hit never extends an entry’s life.
  • GET /api/v1/config/query-log reads back the sample rate that runs. On a node whose configuration file leaves the rate unset it answered 0 while the node logged every query; it now answers 1.
  • A node that joins with an empty store, such as the replacement in a lifecycle swap, now takes every user. Before, it could refuse the edited admin, and then have no default admin either, so nobody could sign in to it as admin.
  • A delete that reaches a node only through the background sync now stops being served there. A conditional forwarder, stub zone, TSIG key, subnet mapping or discovery scope deleted while a node missed the message used to keep being used on that node until a restart: queries still went to the deleted upstream, the deleted TSIG key was still accepted, and the deleted mapping or scope still applied. A deleted health check is also removed from the node’s checker at once.
  • The self-signed bootstrap certificate (API on 8443, DNS over TLS and DNS over HTTPS) renews over the same key. Each renewal in its last 30 days used to generate a new key too, so anything that pinned the node’s public key stopped trusting it about every 23 months. The certificate is still a new one, so a client that trusts the certificate itself (SSL_CERT_FILE, a forwarder’s ca_pem) should fetch it again after a renewal.
  • The Lifecycle page’s upgrade plan names a subnet join permission it does not carry. When the old node’s identity held the role above the subnet, on its virtual network, resource group or subscription, the plan deliberately does not copy it, and the next swap from the new node was refused with nothing to say why. The plan now prints a notice: line naming each inherited role and its scope before the deploy; grant the new node the same before it drives a swap. The plan still never creates a grant.
  • An aborted swap that leaves the stable IP unserved puts that outage first in its error (step SWAPPING failed: STABLE IP <ip> NOT RESTORED: ...; the swap had been aborted: ...), and the Audit Log records it in one lifecycle.swap.recovery row instead of a second lifecycle.swap.failed row.
  • dtm-server restore --local can no longer stop the node starting or corrupt a running one:
    • With --accept-cross-cluster or --insecure-backup it now opens a working copy of the snapshot with the key the node starts with, and refuses one that would stop the node starting (“Failed to install secrets-at-rest key”), naming --source-key-file. Run it with the service’s environment file loaded, as the backup and restore page shows. A restore made through the API is not yet checked this way; see known limitations.
    • It refuses while dtm-server holds the database (“is locked by another process”), before it reads the key or the snapshot. Stop dtm-server first.
    • It refuses, and changes nothing, when a restore or compaction is already staged (dtm.db.restore-pending or dtm.db.restore-trigger exists). Before, a staged restore was silently replaced, and a snapshot that then failed validation left a trigger with no snapshot, so the next start failed with “restore trigger set but sidecar missing”. POST /api/v1/restore still replaces what is staged. To abandon a staged restore, remove both files.
  • A restore whose final swap fails no longer leaves the node without a database. If applying a staged restore failed (a data directory the service cannot write, a full disk), the node started with no database and the next start applied the snapshot anyway. A failed swap now puts the previous dtm.db back and keeps the staged snapshot and trigger, so you can fix the cause and start again; the error says which state the store is in, and if the previous file cannot be put back it names dtm.db.pre-restore so you can move it back by hand. A live file that cannot be moved aside at all stops the swap too. This covers dtm-server restore --local and the swap on server start.
  • A cluster deployed again from scratch can restore its old snapshots. A snapshot is sealed to the replication key of the cluster that took it, and every template generates a new key, so after losing a whole deployment its snapshots could not be restored: restore --local refused them, and --accept-cross-cluster applied them and the node then failed to start. The new dtm-server restore --local --source-key-file <path> takes the old cluster’s key from a file, verifies the snapshot with it, re-encrypts the snapshot’s secrets for this cluster and verifies the result before staging it. A refusal, or a crash before that point, leaves the live store as it was. The key file must be a regular file owned by the user running the command and readable by its owner only, and the key never goes on the command line. Keep the replication key with your backups but away from them: it is the only thing that opens them.
  • The backup, restore and disaster-recovery runbooks match the code. Following them used to fail or silently do nothing:
    • if you set up the cron backup from these docs before 1.0.6, check that it is producing backups: its script pointed --cacert at a file no template writes, so it never took one. It now names the node’s API certificate, /var/lib/dtm/api-tls.crt;
    • the whole-cluster restore now restores on the node with no seeds, readmits each wiped node before starting the next, and checks convergence with the member count and the dtm_storage_zones and dtm_storage_records gauges;
    • the disaster-recovery restore no longer crash-loops node 1: it says where snapshots are, that they are applied with dtm-server restore --local, and that a snapshot restores only under the replication key it was taken with;
    • shell access in the disaster-recovery runbook is SSH through a temporary network security group rule that is removed afterwards;
    • re-sending a zone to trigger a sync no longer overwrites the zone’s TTL on every node;
    • write calls send X-DTM-CSRF: 1, without which the API answers 403.
  • The upgrade, known-limitations, troubleshooting and failure-mode pages match the code. The main corrections that change what you do:
    • replacing a node that is already dead is refused unless retire_node_id or retire_skip is sent; see rolling upgrades;
    • never change replication.encrypt_key one node at a time, because it splits the cluster; there is no live rotation of that key. Rotating it does not invalidate API tokens; removing the node that minted them does;
    • if you move dtm.db aside on a node, remove and readmit the node before it rejoins; see disaster recovery;
    • when you check that all nodes are alive, count the members against the number you expect, because GET /api/v1/nodes lists only current members;
    • two alert examples that could not fire now do, and the drain alert no longer fires at the start of every drain;
    • which secrets a snapshot seals: TSIG, Entra, OAuth2 and OTLP secrets are sealed, health-check bearer headers and mTLS keys are not.
  • Nodes still on 1.0.5: install the Ubuntu ca-certificates update, then restart. The Ubuntu ca-certificates update 20260816ubuntu0.24.04.1 removes the root Ubuntu blocked for this incident, “emSign ECC Root CA - G3” (SHA-256 fingerprint 86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B), so a target of your own whose certificate chain ends at that root will stop verifying. The same update also drops four roots in Mozilla’s routine 2.90 refresh (“Atos TrustedRoot 2011”, “Entrust Root Certification Authority”, “SecureSign Root CA12” and “ePKI Root Certification Authority”), so a target chaining to one of those stops verifying too: check your HTTPS health-check targets and forwarders first. A running process only reads the roots at start, so after apt installs the update, restart dtm-server and dtm-ui, one node at a time (a rolling restart). Check the result with (ca-certificates should be at or above 20260816ubuntu0.24.04.1, and the count should be 0):

    Terminal window
    apt-cache policy ca-certificates
    openssl crl2pkcs7 -nocrl -certfile /etc/ssl/certs/ca-certificates.crt \
    | openssl pkcs7 -print_certs -noout \
    | grep -c '^subject=.*CN = emSign ECC Root CA - G3$' # expect 0

    Tested against Ubuntu’s own packages, the count reads 1 with ca-certificates 20240203 and 0 with 20260816ubuntu0.24.04.1. (ls /etc/ssl/certs | grep -c emSign_ECC_Root_CA_-_G3 is a shorter check that reads the same.)

    On 1.0.6 the binary itself refuses chains through the eight intermediates. Apply the ca-certificates update as well, as normal OS patching.

    Corrected 2026-10-24: an earlier version of these notes named different roots and a different check. The root, the list and the check above are taken from the Ubuntu package and tested against it.

  • A workspace that already has a DTMLogs_CL table needs a one-time migration before the first apply with Logs Ingestion on. That is a table created by the old Data Collector path, and the Terraform module’s own archive table (total_retention_in_days > 0). Run az monitor log-analytics workspace table migrate. With the Terraform module, first run terraform state rm on the module’s retention-only table resource, or the apply resets the archive retention of DTMLogs_CL to the workspace’s analytics retention, and import the migrated table afterwards. The steps, in order, are in deploy/terraform/modules/monitoring/README.md; Bicep users run only the table migrate command (deploy/bicep/README.md). A deployment that never turns Logs Ingestion on is unchanged.

  • Once on, the custom table is protected from destroy. Turning Logs Ingestion off, removing the module or a full terraform destroy then fails the plan with Instance cannot be destroyed, instead of deleting DTMLogs_CL and its rows. The module’s README gives the terraform state rm step that keeps the table.

  • Rotate the Log Analytics workspace’s shared keys after upgrading. A node record stored by an older version keeps the old key in the node’s database until the next PUT /api/v1/config/azure-monitor, and a backup taken before then carries it.

This release includes security improvements; we recommend all customers upgrade.

  • Managed script health checks. DTM can now store a script health check’s script and run it on every node, where before a script check ran a file you placed on one node, and only on that node. A script version runs only if one of your keys signed exactly its body: each node checks the signature against its own script_signing_keys before every run, and never runs a script whose hash is listed in its script_revoked_hashes. DTM never holds the private key. Create a key with dtm-server script-key generate and sign a script with dtm-server script-sign. A check pins one version, so publishing a newer version never changes what a check runs. A check that cannot run is refused by name, its backend stays in service, and the new DTMScriptCheckNotRunning alert fires. Script checks still run only on nodes where allow_script_health_checks is on. In Terraform, the new dtm_script resource takes a script and a signature made outside Terraform, and dtm_healthcheck takes a script_config block that names a managed script.
  • New storage gauges: dtm_storage_zones and dtm_storage_records (the live zones and records on the node), dtm_storage_tombstones (the delete markers held, by type) and dtm_storage_tombstone_oldest_timestamp_seconds (if time() minus this passes 30 days, old delete markers are no longer being purged).
  • A Marketplace node can recover on its own when Microsoft starts signing its instance attestation with a new issuing certificate, as long as its network allows outbound HTTP to www.microsoft.com. A warning in the journal and the counter dtm_entitlement_issuer_fallback_total show when this happened, which is the cue to upgrade before you deploy a node with no outbound internet. Without outbound internet the node still refuses to start, as before, and the journal names the certificate it needed, the address it tried and why it failed.
  • Private Endpoints that report their public name are published. An endpoint with no private DNS zone group reports its resource’s public name, such as myacct.blob.core.windows.net, and DTM now maps it to the matching privatelink zone and record. Endpoints that have a private DNS zone group are read too, from the zone group. When one resource has two endpoints, the record carries both endpoints’ addresses. See Private Endpoints.
  • A Private Endpoint’s public name resolves to its private address through DTM. When a forwarded answer’s CNAME chain enters a privatelink zone that DTM hosts, the rest of the chain is answered from DTM’s own record, as Azure DNS does for a linked private zone. If DTM hosts the zone but not the name, the public answer goes out unchanged and is counted in the new dtm_dns_privatelink_splice_total metric. Set azure.privatelink_splice_enabled: false to turn this off.
  • HTTPS health checks can verify a backend by its hostname. The new tls_server_name field on http_config and health_check_template (API, Terraform and the console) sets the name the probe sends as SNI and checks the certificate against, while the probe still connects to the value’s address. A backend whose certificate names its hostname, not its private IP, can now pass. See the upgrade notes for the Host header.
  • The templates can grant the stable-IP swap its subnet permission. grantSubnetJoin (off by default) and subnetJoinRoleDefinitionId (default Network Contributor) are in every ARM template and Bicep plan, and in a new Upgrades step of the Marketplace wizards. With the grant on, each node’s managed identity gets the role on each cluster subnet outside the deployment resource group, scoped to that subnet alone.
  • New metric dtm_cluster_capability_open{capability}: 1 while a behaviour that waits for every node to support it is switched on, 0 while it waits.
  • Zone, record and override changes reach every node within seconds. Before, on larger clusters some nodes could lag until the next background sync. Sign-ins and session refreshes now reach every node within seconds too, so a refresh served by a node other than the one you signed in on no longer signs you out, unless that node could not be reached when you signed in.
  • The background sync between nodes runs every 15 seconds by default, instead of 30. Every other change, such as users, TSIG keys, health checks and forwarders, now almost always reaches every node within 40 seconds on a cluster of up to 12 nodes. This doubles the sync traffic between nodes. To keep the old interval, set push_pull_interval to 30s with PUT /api/v1/config/replication, or replication.push_pull_interval_seconds to 30 in the configuration file.
  • HTTP and HTTPS health checks open a new connection for every probe. A backend that stops accepting new connections, for example behind a new network security group deny rule, a drained listener or a route change, is now marked unhealthy. Before, probes reused one kept-alive connection, which such a change does not close.
  • The Entra SSO configuration is cluster-wide. One save, from the Settings page, the API or Terraform’s dtm_sso_config, now configures every node. Before, it reached only the node that served it, so rotating the client secret broke sign-in on the other nodes. See the upgrade notes.
  • Sign in with Microsoft completes on any node of a cluster once every node runs 1.0.5. It used to fail with “invalid or expired state” unless the sign-in started and finished on the same node. A sign-in started on another node’s address is first sent to the redirect URI’s host.
  • The Entra client ID must be a UUID, as the tenant ID already had to be. A saved configuration keeps working until it is saved again.
  • Changing a forwarder, a stub zone, the default forwarder or the forwarder allowlist takes effect at once. The cache of forwarded answers is now cleared on such a change, where before clients could keep getting the old answers until each cached entry expired (up to an hour).
  • Forwarded answers carry only records owned by the queried name, or by a name its CNAMEs reach, from the first answer on. A negative answer through a CNAME now keeps the target zone’s SOA record, so downstream resolvers can cache it, and the upstream’s extended error codes and DNSSEC OK flag reach the client.
  • An ALIAS record takes only addresses on its target’s own CNAME chain, at most 8 CNAMEs. A loop or a longer chain answers NODATA.
  • An ALIAS record keeps its last good addresses for up to 5 minutes when a refresh times out or gets SERVFAIL, instead of answering NODATA until the next refresh. Health checks keep running on those addresses.
  • A region written as an Azure display name matches its location: “West Europe” now matches westeurope everywhere DTM compares regions, and GET /api/v1/regions lists each region once. The Terraform provider warns at plan time on a region that is not in the canonical form. See the upgrade notes for the dtm_node_info label.
  • The Cluster permissions card checks the subnet permission the stable-IP swap needs on every subnet a swap from this node can target, including the subnet of a node that is down but not removed. GET /api/v1/lifecycle/identity is now admin only.
  • Tag-based VM registration now covers management-group discovery scopes, and pauses, rather than removes, the addresses behind a disabled discovery scope, as region discovery already did. A failed Azure Resource Graph query now fails the cycle and removes nothing, and POST /api/v1/tag-registration/run answers 502 instead of 200 with zero counts.
  • On a partial region discovery cycle, the preview proposes exactly the removals Apply makes.
  • A health check refuses two spellings of the same header, such as Host and host.
  • Template, wizard and Settings page text says replication traffic on the replication port (7946), and the replication encryption key. The Settings page’s Replication Timing fields read Broadcast interval, Broadcast fan-out and Dead-peer send window. Setting names, API fields and resource names are unchanged.
  • dtm-server no longer stops while its database grows. A storage metrics sample taken while the database file was growing could stop the process, most often during a restart or a node’s first sync.
  • dtm_storage_bbolt_pages_in_use reports the pages actually in use. It used to count the whole database file, which grows ahead of the data, so it read high by up to about 2x. See the upgrade notes for the alert this affects.
  • Removing a node and then stopping it now completes the removal. Before, the cluster kept counting the removed node: it stayed in the longest-unreachable-member gauge and its alert, until you repeated the removal with ?force=true.
  • dtm_dns_dot_listener_up no longer reads 0 on a healthy node after the DNS over TLS listen address is changed.
  • Audit log entries written in the same instant now list in the order they happened. New entry IDs end in a zero-padded sequence number; existing entries keep their IDs.
  • The four-node, two-region Marketplace wizard’s subnet tooltips now name the right nodes: nodes 1 and 2 in the primary region, nodes 3 and 4 in the secondary.
  • An A query for a name that holds a CNAME in a DTM zone now gets the target’s address, not just the CNAME. A target inside DTM is answered from DTM’s own data, with its own health and region selection; one outside DTM is resolved through the forwarders when the client asks for recursion. A loop, or a chain of more than 8 CNAMEs, answers SERVFAIL.
  • Narrowing adminAddressPrefix no longer cuts node-to-node 8443, so a node with a corrupt database can confirm a healthy peer and reseed. Every template that has the management deny rule now allows 8443 from the cluster’s own subnets, from the same sources as its replication rule. See the upgrade notes for existing deployments.
  • A stable-IP swap that fails after the old VM is deallocated puts the stable IP back on the old VM and restarts it, whatever the error, or gives the manual recovery steps when it cannot. A swap is also checked for the subnet permission it needs before it changes anything, and Azure’s “another operation in progress” is retried twice before the swap gives up.
  • The Lifecycle page’s deployment plan for a replacement node now keeps the old node’s region, VM size, zone, data disk and network security group sources, reads the replication sources from the old node, deploys into the old node’s subnet, and runs when pasted into zsh.
  • One unreadable subscription no longer stops Private Endpoint cleanup in every subscription. A record is now removed when every subscription its addresses came from was read in full.
  • A manual region discovery Apply takes effect at once on the node that applied it, without a restart.
  • An OAuth2 health check accepts a token response whose expires_in is a string.
  • The shipped Grafana dashboard’s per-node and region-discovery panels show data again on any Prometheus.
  • An upper-case Entra tenant ID no longer fails sign-in, and Terraform’s dtm_sso_config refuses allow_meta_tenant = true at plan time instead of at apply.
  • A gallery copy of the Marketplace image keeps its purchase plan on the Terraform and Bicep image paths, so the VM is created and starts. See the upgrade notes.
  • A default forwarder configuration saved before the first release is no longer rewritten on every node every 15 seconds.
  • On a Terraform deployment, first boot no longer waits indefinitely for a stalled metadata service: the wait is bounded to about three and a half minutes.
  • Managed values on records maintained by Private Endpoint automation or tag registration cannot be changed through the records API or JSON import, and editing a managed record keeps them.
  • The freelist-bloat alert can fire after you upgrade. Because pages in use are now counted correctly, dtm_storage_bbolt_free_page_ratio reads higher for the same database, so DTMBboltFreelistBloat (or the Azure Monitor bbolt-freelist-bloat rule) can fire on a node where it did not before. When it does, more than half of the database really is free pages: compact the node.
  • While some nodes still run 1.0.4, a user who signs in within 5 minutes of their sessions being ended can be asked to sign in once more at the first refresh. Finish the upgrade and remove any node you have retired. See Known limitations.
  • Rolling a node back to 1.0.4 can ask some users to sign in again, at the first refresh of a session that node learned of only after the rollback.
  • The 15-second sync interval applies to each node as you upgrade it. A node on 1.0.4 keeps 30 seconds until it is upgraded.
  • Before you roll a node back to 1.0.4 with Private Endpoint automation, turn the automation off on that node, and do not call POST /api/v1/discovery/private-endpoints/run on it afterwards. An older node removes every record that newer nodes published from public names, and the removal reaches every node.
  • Public-name and zone-group Private Endpoint records are published once every node runs 1.0.5. Until then they are held and counted as held in the run response. If held stays above zero after the upgrade, a node that is down was never removed: remove it.
  • When you move to DTM, keep the Azure privatelink zones that a private DNS zone group populates. Do not delete or unlink them.
  • If your VNet is in a resource group other than DTM’s, give each node’s managed identity Network Contributor, or a role with only Microsoft.Network/virtualNetworks/subnets/join/action, on the cluster subnet before the first stable-IP swap, or turn on grantSubnetJoin. Without it, a swap is refused before it changes anything.
  • A Host header now also sets an HTTPS check’s TLS name when tls_server_name is not set. A check that sends a Host header to a backend whose certificate names only its IP now fails verification: give the backend a certificate for that hostname, or remove the header if the backend does not need it.
  • The region label of dtm_node_info uses the canonical form, for example westeurope for a node configured as “West Europe”. Update a dashboard or alert that filters on the old spelling.
  • On the Terraform or Bicep image path, if your own image carries no purchase plan, set image_id_purchase_plan = false (or imageResourceIdPurchasePlan = false) before your next apply. Otherwise the new default adds a plan and the VMs are planned for replacement.
  • The first Entra SSO save after every node is upgraded replaces every node’s configuration, including any per-node redirect URI. The Settings page says when the nodes’ configurations differ. Until that save, each node keeps its own.
  • Tag registration pauses for a disabled scope once every node runs 1.0.5. An older node that runs a cycle during the upgrade still removes those addresses.
  • Clusters deployed before 1.0.5 keep their network security groups: an upgrade adds no 8443 rule. Until you add one, keep adminAddressPrefix wide enough to cover the node subnets, or add the 8443 rules by hand (see known limitations). Do not redeploy the original plan for this: it replaces the rule list and drops rules added by hand.
  • A re-applied alerting sample shows an in-place update to the cluster members rule, whose description now says replication traffic.

This release includes security improvements; we recommend all customers upgrade.

Version 1.0.3 was not published to the Azure Marketplace. 1.0.4 is the first Marketplace release after 1.0.2, and it includes every change made in 1.0.3, listed below.

  • The cluster-traffic source parameter is now a list of extra subnets. The single-region ARM template’s gossipSourceAddressPrefix is removed and replaced by gossipSourceAddressPrefixes, a list of CIDRs that is added to the node subnet and never replaces it. In the Terraform single-region sample, gossip_source_address_prefix becomes the list gossip_source_address_prefixes. In the Bicep node module, gossipSourceAddressPrefixes is renamed gossipSubnetPrefixes, alongside the new gossipPeerPrefixes and gossipExtraPrefixes. Update your parameter files before you redeploy.
  • ARM templates: adminUsername has no default. A deployment from the Azure CLI or the API must pass it. The Azure portal wizards and the add-node command on the Lifecycle page already do. Bicep and Terraform keep their defaults.
  • Microsoft Entra ID sign-in needs your directory (tenant) ID. The values common, organizations and consumers are refused, and so is allow_meta_tenant set to true. A node whose configuration still names one starts with Entra ID sign-in off and logs a warning; local sign-in is unaffected. Set tenant_id to your directory ID and remove allow_meta_tenant. GET /api/v1/auth/entra/config now reports active, and inactive_reason when sign-in is off.
  • A CNAME at the zone apex is refused; use an ALIAS record. The API refuses a new apex CNAME and a rename that moves a CNAME onto the apex, and the importers skip one and name it in the zone’s errors. A CNAME already at the apex keeps serving and can still be edited in place.
  • Managed identity health probes need the admin role, and some audiences are refused. Creating or changing a check, record or override that uses a managed identity probe now needs the admin role. By default a probe can no longer request a token for Key Vault, Managed HSM, Resource Manager, Microsoft Graph, Azure AD Graph or Storage. Your own application’s audience is unaffected, and managed_identity_probe_audiences in the server configuration file sets an exact list instead.
  • Health checks refuse 168.63.129.16 and link-local addresses (169.254.0.0/16 and fe80::/10), both when a check is saved and when it probes. DNS forwarding to 168.63.129.16 is unaffected.
  • Moving a stored health-probe credential needs it entered again. A user without the admin role who changes where a credential is sent must send the credential again. That means a check’s protocol, target or OAuth2 token URL, a record or override template’s protocol, port, token URL or a new value, or turning on a record’s auto_populate. Admins are not affected. A dynamic DNS update can no longer add a new value to a record whose template carries a credential or uses a managed identity; re-adding a value the record already holds, such as a DHCP renewal, still works.
  • The self-signed certificate for the API, DNS over TLS and DNS over HTTPS lasts two years and renews itself in its last 30 days, without a restart. A certificate you uploaded is never replaced. A new metric, dtm_tls_certificate_expires_in_seconds, and a new alert, DTMTLSCertificateExpiringSoon, warn before a served certificate expires.
  • A new alert, DTMReplicationQueueBacklog, fires when a node’s replication queue stays at 5000 messages or more for 15 minutes while it has peers. With this and the certificate alert, the Azure Monitor alerting samples carry 32 rules per node VM.
  • Limits on concurrent inbound cluster connections: 4 per source address and 32 in total by default. Set them with replication.gossip_max_streams_per_source and replication.gossip_max_streams.
  • Every deployment template’s network security group denies the cluster port (7946, TCP and UDP) from the rest of the virtual network. The allow rules name the cluster’s subnets, so a node you add later in the same subnet joins with no rule change. A node in another subnet or region needs that subnet added to the existing nodes’ allow rules. The add-node wizard has a new optional Other cluster subnets field: when you add a node to a dual-region cluster, enter the other region’s subnet there.
  • Deleting a node’s VM deletes its data disk on clusters deployed from the Azure portal or the ARM templates. In a cluster of two or more nodes, the data lives on the other nodes. On a single node, deleting the VM deletes its zones and records, so keep backups if you may delete or rebuild it.
  • /readyz no longer reports a node not ready because its replication queue is deep. It still names the depth, and a node that has fallen behind its peers is still reported not ready.
  • api_rate_limit.trusted_proxies defaults to loopback (127.0.0.1/32 and ::1/128) when the key is absent, so each console browser has its own rate limits. [] trusts nobody, and a list replaces the default; if a load balancer also fronts the API, list it and keep loopback.
  • Audit entries and sessions record the browser’s address for console actions, not 127.0.0.1. A direct API caller’s entry keeps its own host:port, so ip_address holds either a bare IP or host:port.
  • Replacing a failed node with a Lifecycle swap asks which cluster identity to retire. Such a swap is refused when it is requested, with retire_candidates; send it again with retire_node_id, or with retire_skip to leave the identity in place. The swap history gains a Cluster identity column.
  • Session refresh is faster once every node runs 1.0.4. Existing sessions keep working.
  • The Terraform provider’s resource and attribute descriptions read as plain documentation.
  • A Marketplace node could fail to start after Azure changed the certificate that signs its instance attestation. Fixed; no action needed. The attestation must still chain to the same pinned Microsoft root.
  • The web console waits up to 30 seconds for a session refresh, so a slow refresh no longer sends the operator to the sign-in page as easily.
  • A script check whose command fails to start reports its own error at once, instead of “runner did not answer in time”.
  • Terraform: a dtm_user update no longer shows source as “(known after apply)”.
  • The dual-region ARM and Bicep templates, deployed from the Azure CLI, could fail at the VNet peering step when one region’s subnet already existed. The Azure portal wizards create no peerings and were not affected.
  • After a large zone was deleted, a node could take much longer than its peers to become ready after a restart.
  • A node that missed a node removal now still applies it, even after the node that performed the removal has itself been removed.
  • Managed identity checks: on each upgraded node, a check whose audience is now refused turns unhealthy. Point it at your application’s own audience, or list the audiences you allow in managed_identity_probe_audiences.
  • Health checks aimed at 168.63.129.16 or a link-local address fail on each upgraded node and cannot be saved again. Point them at the backend you meant to monitor.
  • Rolling a node back to 1.0.2 after every node runs 1.0.4 can sign out users whose session renewed since then.
  • Terraform configurations that still hold the *** placeholder for a probe secret need the real secret applied before a non-admin token can change that check’s destination.
  • Existing clusters keep their network security groups until you redeploy or edit them.
  • Nodes deployed from the 1.0.2 or earlier templates keep their data disk when the VM is deleted, including when a Lifecycle swap deletes the retired node’s VM. After such a swap, delete the retired node’s data disk once you have verified the upgrade. It is named like that node’s VM with -data in place of -vm (for example dtm-ha-n1-data beside dtm-ha-n1-vm), and it holds a copy of the cluster’s data and the retired node’s key material. Nodes deployed from the 1.0.4 templates delete it with the VM.
  • If a host or forwarder pins the self-signed API certificate, fetch it again after it renews. Existing nodes renew their one-year certificate in its last 30 days, once they run 1.0.4.
  • To get the two new alerts, redeploy the Azure Monitor alerting sample or reload the Prometheus rules.
  • Azure Monitor counters are now sent as the increase in each interval, not as a running total. Read them with Sum (Total). A chart or alert that reads a counter with Max now sees per-interval values, and must switch to Sum (Total).
  • The alerting samples no longer take a data_volume_bytes parameter. Their disk rules now compare the free-space ratio. Remove the parameter from your parameter set before you redeploy an existing copy.
  • Manage the certificate DTM serves on the API, DNS over TLS and DNS over HTTPS from Terraform, with the provider’s new dtm_tls_certificate resource. Its private key is a write-only argument (Terraform 1.11 or later), so it never reaches Terraform state. Destroying the resource removes it from state only: the cluster keeps serving the certificate.
  • The Terraform provider can trust a private certificate authority. Set ca_cert_pem to your CA’s PEM, and the provider verifies the API against it instead of the system trust store, even if insecure_skip_verify is also set.
  • The web console’s self-signed certificate (port 8080) renews itself in the last 30 days before it expires, on deployments from the Azure portal, ARM and Bicep. A certificate you supplied is never replaced.
  • On Terraform deployments, the console certificate follows its Key Vault secret. Add a new version of the secret, and every node applies it within about an hour, with no SSH. The default self-signed console certificate renews itself in Key Vault before it expires.
  • The Azure Monitor alerting samples now cover every alert rule of the Prometheus rule set: 30 rules per node VM, up from 16. Two new optional parameters set the DNS latency thresholds, dns_latency_p99_threshold_seconds and dns_latency_p95_threshold_seconds.
  • The ARM templates declare each node’s data disk on the VM itself, as Microsoft’s Marketplace guidance requires. A node’s VM and its data disk are always in the same zone.
  • A node whose first-boot secrets are not available yet retries about once a minute until it succeeds, and picks up a secret created later with no action on the node. It never stopped after an hour, as earlier documentation said.
  • Built with Go 1.26.9 and updated Go networking libraries.
  • A DNS listener that went down while the node was starting could be missed by the readiness check (/readyz), which then reported the node ready with that transport unavailable.
  • Upgrade every node to 1.0.2 before you deploy the 1.0.2 alerting samples. A node on an earlier version can make the stale-lock rule fire continuously.
  • Terraform: apply one node at a time. 1.0.2 changes how the module encodes each node’s custom data, so terraform plan shows every node’s VM replaced. Apply each node with -target and wait for it to rejoin before the next, as the rolling-upgrade guide describes. A plain terraform apply would replace every node at once.
  • ARM: do not redeploy a 1.0.2 template over a 1.0.1 deployment. Azure refuses it (PropertyChangeNotAllowed on the data disk) and changes nothing. Upgrade existing nodes with the Lifecycle swap.

The first release of Hayami DTM on the Azure Marketplace.

  • Health-checked DNS for private Azure virtual networks. DTM answers authoritatively for your zones on the nodes’ private IP addresses, and removes a backend from its answers when that backend fails its TCP or HTTP(S) health check.
  • Record types A, AAAA, CNAME, MX, TXT, NS, PTR, SRV and CAA, plus ALIAS records that point a name at other hostnames and return their healthy addresses.
  • Load balancing per record or per zone: round-robin, failover, region-aware failover, latency-based, client affinity, and weighted, which splits traffic between addresses by percentage for canary or blue-green rollouts.
  • Region-aware answers: clients are given backends in their own region first, and fail over to another region only when every local backend is unhealthy.
  • Clusters of several nodes, in one region or two. The cluster is leaderless: a change made on any node reaches the others, with no central server.
  • Conditional forwarding for chosen domains, stub zones, and dynamic DNS updates (RFC 2136) authenticated with TSIG.
  • Optional DNS over TLS and DNS over HTTPS.
  • A web console and a REST API for zones, records, health checks and settings, with optional Microsoft Entra ID sign-in.
  • Metrics for Prometheus, metrics to Azure Monitor, query logs to Log Analytics, and alerting samples for Azure Monitor in Terraform, Bicep and ARM.
  • Secure defaults. The administrator password is generated on the node at first boot and kept in a Key Vault the deployment creates; that vault is private by default, with public network access off and the nodes reaching it through a private endpoint. SSH to the nodes is denied from inside the virtual network by default, and accepts keys only.