This page is the reference for the server configuration file,
/etc/dtm/dtm-server.yaml on a deployed node. The deployment writes a working
file for you at first boot; you only edit it for the boot-time settings below
that have no API surface.
Strict validation. An unknown or misspelled key anywhere in the file
stops startup with an error naming the offending field, so a typo fails
loudly instead of being silently ignored (see
troubleshooting).
An empty or comments-only file is fine: the server starts with defaults.
Restart to apply. File edits take effect at the next service restart.
Restart one node at a time: a node stops answering DNS while it
restarts.
Environment overrides. Keys marked with a DTM_* name below can be set
as environment variables, which win over the file. List values are
comma-separated. A malformed value stops startup rather than being ignored.
The deployment uses this convention itself to deliver secrets (the cluster
encryption key, the Entra client secret) from Key Vault into a root-only
environment file, so secret values never appear in the YAML.
The file is per node. Apply the same edit on every node unless a setting
is genuinely node-specific (listen addresses, node identity).
Most day-to-day settings are not in this file. Zones, records, health
checks, forwarders, the blocklist, subnet mappings, and TSIG keys are managed
through the API, Terraform,
and the UI, and replicate cluster-wide. Several config sections below also
have a runtime endpoint that is the preferred surface; for those, the YAML is
only the initial seed, and once a runtime change has been made the persisted
value wins over the YAML at the next boot:
UDP DNS bind address. The deployment binds the VM’s private IP, not all interfaces.
listen.dns_tcp
":53"
DTM_LISTEN_DNS_TCP
TCP DNS bind address.
listen.api
":8443"
DTM_LISTEN_API
REST API (HTTPS) bind address.
listen.dot.enabled
false
none
Serve DNS-over-TLS on listen.dot.listen_addr. Boot default only; the runtime toggle wins.
listen.dot.listen_addr
":853"
none
DoT bind address (host:port).
listen.doh.enabled
false
none
Serve DNS-over-HTTPS (/dns-query) on a dedicated listener at listen.doh.listen_addr. Boot default only; the runtime toggle wins. Unauthenticated by spec: gate port 443 at the NSG first.
listen.doh.listen_addr
":443"
none
DoH bind address (host:port).
listen.tls.cert_file
empty
DTM_TLS_CERT_FILE
TLS certificate for the API (and DoT/DoH). Deployed nodes set /var/lib/dtm/api-tls.crt with managed: true. Empty with managed off: a fresh self-signed certificate at every start.
listen.tls.key_file
empty
DTM_TLS_KEY_FILE
Private key matching cert_file (/var/lib/dtm/api-tls.key as deployed). Owner-readable only; set both keys together. A missing file stops startup, unless managed is on and both are missing.
listen.tls.managed
false
DTM_TLS_MANAGED
true as deployed: if both files are missing, writes a two-year self-signed pair once, and renews it in its last 30 days; serves the uploaded certificate.
listen.tls.ca_file
empty
DTM_TLS_CA_FILE
Advanced: enables mutual TLS. Every API client (including each node’s own UI service) must then present a certificate signed by this CA.
listen.dns_listeners
1
none
DNS listener socket replicas for high query rates. Worth raising toward the vCPU count on 8+ vCPU nodes.
The cluster section. The deployment writes this for you; the main reason to
touch it is the readiness threshold.
Key
Default
Env override
What it does
replication.enabled
false (true as deployed)
DTM_REPLICATION_ENABLED
Enable clustering.
replication.bind_addr
the node’s private IP as deployed
none
Cluster traffic bind address.
replication.bind_port
7946
DTM_REPLICATION_BIND_PORT
The cluster port (allow it node-to-node in the NSG).
replication.seeds
empty
DTM_REPLICATION_SEEDS
host:port of existing nodes a joining node contacts.
replication.encrypt_key
required
DTM_REPLICATION_ENCRYPT_KEY
The cluster encryption key. The deployment delivers it from Key Vault via the environment; every node must hold the same value.
replication.push_pull_interval_seconds
15
DTM_REPLICATION_PUSH_PULL_INTERVAL_SECONDS
Full-state sync cadence between nodes. Also settable via PUT /api/v1/config/replication (applied on restart).
replication.readyz_queue_threshold
5000
DTM_REPLICATION_READYZ_QUEUE_THRESHOLD
Outbound update-queue depth at which /readyz names the backlog. From v1.0.4 it is reported, not a reason to turn not-ready; see known limitations.
replication.gossip_max_streams_per_source
4
DTM_REPLICATION_GOSSIP_MAX_STREAMS_PER_SOURCE
Concurrent inbound cluster-port connections accepted from one source address (added in v1.0.4). A connection over the cap is closed before any of it is read, and counted in dtm_replication_gossip_stream_refused_total.
replication.gossip_max_streams
32
DTM_REPLICATION_GOSSIP_MAX_STREAMS
Concurrent inbound cluster-port connections accepted in total (added in v1.0.4). Normal cluster traffic stays well under both caps; a refused connection costs one sync, retried on the next interval.
The built-in backup scheduler; the workflow lives on
backup and restore.
These keys are the boot default: a setting saved per node with
PUT /api/v1/config/backup, the Settings page, or dtm_backup_config
overrides them at the next boot. The one exception is the passphrase: when the
saved setting has none, backup.passphrase or DTM_BACKUP_PASSPHRASE still
applies.
Key
Default
Env override
What it does
backup.schedule_enabled
false
DTM_BACKUP_SCHEDULE_ENABLED
Take snapshots on a schedule, per node.
backup.interval_minutes
0 = 1440 (daily)
DTM_BACKUP_INTERVAL_MINUTES
Snapshot cadence.
backup.retention_count
7
DTM_BACKUP_RETENTION_COUNT
Local snapshots kept; oldest pruned first. Pruning matches by filename: any dtm-backup-*.dtmb in backup.dir counts, including a downloaded snapshot saved there under its default name, so rename manual copies or store them outside backup.dir.
backup.dir
a backups directory beside the data store
DTM_BACKUP_DIR
Where local snapshots land.
backup.blob_container_url
empty (local-only)
DTM_BACKUP_BLOB_CONTAINER_URL
Optional off-node copy via the VM’s managed identity (Storage Blob Data Contributor). A URL carrying a query string (SAS) is rejected at startup.
The observability guide and
tag-based VM registration cover these in context.
The Azure Monitor and Logs Ingestion keys are boot defaults: a setting saved per
node with PUT /api/v1/config/azure-monitor, the Settings page, or
dtm_azure_monitor_config overrides them at the next boot.
azure.subscription_id has no runtime surface and stays a file setting.
Key
Default
Env override
What it does
observability.query_log_enabled
false
DTM_QUERY_LOG_ENABLED
Per-query logging. Boot seed only; prefer PUT /api/v1/config/query-log (live, cluster-wide).
observability.query_log_sample_rate
log every query
none
Keep 1-in-N queries on high-QPS nodes. Runtime-settable on the same endpoint.
observability.audit_max_entries
10000
none
Audit log entry cap, oldest pruned first; minimum 1000. Changed in v1.0.7: the default becomes 2000000.
observability.audit_retention
none (no time limit)
none
Audit log time cap as a duration, for example "2160h" for 90 days. Changed in v1.0.7: the default becomes 2160h (90 days); see audit log retention.
Master switch for the Azure Monitor exporter: custom metrics and query-log shipping. Leave monitor_resource_id and monitor_region blank to publish to this VM, read from the Azure Instance Metadata Service; set them only to send the metrics to a different resource.
azure.monitor_metrics_enabled
true
DTM_AZURE_MONITOR_METRICS_ENABLED
Push custom metrics. false is the logs-only setup: no metric is pushed and no target is resolved, while the query log still ships.
azure.logs_ingestion_endpoint
empty
none
Added in v1.0.6. The logs ingestion URI of the data collection endpoint (or Direct DCR) the query log ships to: an https:// origin on a host under .monitor.azure.com. Set with logs_ingestion_dcr_immutable_id; requires azure.monitor_enabled. See Ship the query log to Log Analytics.
azure.logs_ingestion_dcr_immutable_id
empty
none
Added in v1.0.6. The data collection rule’s immutable ID, dcr- and 32 lower-case hex digits. The node’s managed identity needs Monitoring Metrics Publisher on that rule.
azure.logs_ingestion_stream
Custom-DTMLogs
none
Added in v1.0.6. The stream the rule declares, Custom-<name>.
azure.log_analytics_workspace_id / _key
removed
none
Removed in v1.0.6. A value is logged as an ERROR at start-up that names the replacement, and is otherwise ignored; nothing ships through it. See upgrading from 1.0.5.
azure.region_discovery_enabled
false (true as deployed)
DTM_AZURE_REGION_DISCOVERY_ENABLED
Discover subnet-to-region mappings from your VNets.
azure.region_discovery_resource_group
empty
DTM_AZURE_REGION_DISCOVERY_RESOURCE_GROUP
Scope discovery to one resource group.
azure.region_discovery_mode
arm-list
DTM_AZURE_REGION_DISCOVERY_MODE
Discovery backend (arm-list or resource-graph); runtime-switchable.
azure.region_discovery_auto_apply
false
none
Apply discovered mappings on the timer, as earlier releases did. Default is detect-only: the loop reports drift and an admin applies it.
azure.region_discovery_interval_seconds
3600
none
Detection cadence. An explicit 0 disables the periodic detector (detect on request only); omitting the key keeps the default.
azure.tag_registration_*
disabled
DTM_AZURE_TAG_REGISTRATION_*
Tag-based VM registration. Boot seed only, except tag_registration_client_id; prefer PUT /api/v1/config/tag-registration (cluster-wide). Keys and workflow in that guide.
Entra ID single sign-on covers setup end to end. These
keys are the boot default: a configuration saved with
PUT /api/v1/auth/entra/config, the Settings page, or dtm_sso_config applies
live and overrides them at every later boot: on the node that served it, or on
every node once the configuration is
cluster-wide
(v1.0.5).
Key
Default
Env override
What it does
entra.tenant_id
empty
none
Your directory (tenant) ID, a GUID. SSO activates when tenant, client, secret and redirect URI are all set. The meta-tenants common, organizations and consumers are not accepted: from v1.0.4 a node configured with one keeps Entra sign-in disabled and logs a warning. allow_meta_tenant has no effect.
entra.client_id
empty
none
The app registration’s client ID.
entra.client_secret
empty
DTM_ENTRA_CLIENT_SECRET
The client secret; deliver it via the environment, never in the YAML.
Allow script health-check probes. See the caution below before enabling.
script_signing_keys
empty
none
Public keys that may approve managed scripts: a list of {id, public_key}. Read at startup; keep it the same on every node.
script_revoked_hashes
empty
none
Content hashes (sha256:<hex>) of managed scripts that must never run, whoever signed them. Read at startup; keep it the same on every node.
api_allowed_hosts
empty (no enforcement)
none
Host-header allowlist for the API, a DNS-rebinding defence. The server warns at startup when unset.
api_rate_limit.*
built-in defaults
none
Per-IP API rate limits by class (auth_rps/auth_burst, write_rps/write_burst, read_rps/read_burst, upload_rps/upload_burst); the upload class covers the disk-staging endpoints (/api/v1/restore, /api/v1/admin/compact) with a deliberately severe built-in default (1 request per 600 s). Prefer PUT /api/v1/config/rate-limits.
api_rate_limit.trusted_proxies
loopback (127.0.0.1/32, ::1/128)
none
CIDRs allowed to set X-Forwarded-For; from a trusted peer the rate limiter keys on the address that proxy saw. Deliberately file-only. Omitted, it trusts loopback only, so the web console on the same node gives each browser its own rate-limit bucket. [] trusts nobody: every console user then shares one bucket. A list replaces the default, so keep loopback in it when you add a load balancer’s range. Changed in v1.0.4 (the default was trust nobody).
managed_identity_probe_audiences
empty (built-in denylist)
none
Token audiences a managed_identity health probe may use. Empty: any audience except Key Vault and Managed HSM, Resource Manager, Microsoft Graph and Azure AD Graph, and Storage. Set: only the audiences listed. File-only, added in v1.0.4; see authenticated health probes.
axfr.enabled
false
none
Allow zone transfers (AXFR).
axfr.allowed_cidrs
empty (all, when enabled)
none
Restrict transfers to these networks. Write each entry as a CIDR with its prefix length (10.0.0.5/32, not 10.0.0.5). After you set the list, attempt a zone transfer from a host outside it, and confirm the transfer is refused.
axfr.require_tsig
false
none
Require TSIG-signed, per-zone-authorized transfer requests.
axfr.notify_targets
empty (no NOTIFY)
DTM_AXFR_NOTIFY_TARGETS
host:port list sent an RFC 1996 DNS NOTIFY on every zone-content change, so mirrors stop polling SOA serials.
axfr.notify_tsig_key_name
empty (unsigned)
DTM_AXFR_NOTIFY_TSIG_KEY_NAME
Sign NOTIFYs with this stored TSIG key. Key rotation applies without a restart; a missing key fails closed rather than sending unsigned.
axfr.notify_debounce_seconds
0 = 5
DTM_AXFR_NOTIFY_DEBOUNCE_SECONDS
Per-zone quiet window: a bulk import produces one NOTIFY, not hundreds. Like the rest of axfr, the NOTIFY keys are boot seeds for the runtime record (PUT /api/v1/config/axfr, live, cluster-wide).
forwarder_hardening.qname_allowlist
empty (no restriction)
none
Suffix allowlist for names the default forwarder will resolve.
The file can seed zones and records at first boot (zones: with name,
default_ttl, soa, records), but in production zones are owned by the
API, Terraform, and the UI once the cluster is
running. Do not manage ongoing DNS data through the file.