Skip to content

Configuration reference

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:

YAML section Preferred runtime surface Takes effect
forwarders PUT /api/v1/forwarders/default Live
forwarder_hardening PUT /api/v1/forwarders/hardening Live
api_rate_limit (except trusted proxies) PUT /api/v1/config/rate-limits Live
axfr PUT /api/v1/config/axfr Live
dynamic_dns (enable and TSIG gate) PUT /api/v1/config/dynamic-dns Live
cache PUT /api/v1/config/cache Live
listen.dot / listen.doh PUT /api/v1/transport-config (guide) Live
replication timing PUT /api/v1/config/replication On next restart
azure.region_discovery_mode PUT /api/v1/config/region-discovery Live
observability.otlp PUT /api/v1/observability/otlp Live
observability.query_log_* PUT /api/v1/config/query-log Live
Key Default Env override What it does
listen.dns_udp ":53" DTM_LISTEN_DNS_UDP 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). Empty means a fresh self-signed certificate at every service start; set both file keys for a stable, trusted cert.
listen.tls.key_file empty DTM_TLS_KEY_FILE Private key matching cert_file. Must be owner-readable only; both keys must be set together, and a missing file stops startup.
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.
Key Default Env override What it does
node.id the hostname DTM_NODE_ID Cluster-unique node identity.
node.region empty DTM_NODE_REGION Region label for region-aware routing.
storage.type bbolt DTM_STORAGE_TYPE Local data store type. Production deployments use the default.
storage.path /var/lib/dtm/dtm.db as deployed DTM_STORAGE_PATH Data store path (on the dedicated data disk).
storage.auto_reseed_on_corruption on DTM_STORAGE_AUTO_RESEED_ON_CORRUPTION Boot-time self-heal: a corrupt data store is quarantined and re-seeded from a confirmed-healthy peer, never wiped without one.
forwarders 168.63.129.16 as deployed DTM_FORWARDERS Default recursive upstreams. Runtime-managed via the forwarders API; treat the YAML as the seed.

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 built-in default 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 reports draining during bulk writes; see known limitations.

See the runtime API (/api/v1/config/dynamic-dns) for the enable and TSIG gate; the scavenger tunables are file-or-environment only.

Key Default Env override What it does
dynamic_dns.enabled false DTM_DYNAMIC_DNS_ENABLED Accept RFC 2136 dynamic updates.
dynamic_dns.require_tsig false DTM_DYNAMIC_DNS_REQUIRE_TSIG Reject unsigned updates. Turn this on wherever untrusted clients can reach port 53.
dynamic_dns.scavenge_max_age 0 (disabled) DTM_SCAVENGE_MAX_AGE Seconds after which un-refreshed dynamic records are removed.
dynamic_dns.scavenge_interval 3600 DTM_SCAVENGE_INTERVAL How often the scavenger sweeps.

The built-in backup scheduler; the workflow lives on backup and restore.

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. Only scheduler-written snapshots are pruned.
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.

Outbound event notifications; contracts and event types live on notifications.

Key Default Env override What it does
notifications.enabled false DTM_NOTIFICATIONS_ENABLED Enable event delivery. At least one sink must be set, or startup fails (never silently un-covered).
notifications.webhook_url empty DTM_NOTIFICATIONS_WEBHOOK_URL HTTPS webhook sink (https:// required except loopback).
notifications.webhook_secret empty (unsigned) DTM_NOTIFICATIONS_WEBHOOK_SECRET HMAC-SHA256 signing key for the webhook; deliver via the environment. Empty sends unsigned (startup WARN).
notifications.eventgrid_topic_endpoint empty DTM_NOTIFICATIONS_EVENTGRID_TOPIC_ENDPOINT Azure Event Grid custom-topic sink, published with the managed identity (EventGrid Data Sender). A query string (key) in the URL is rejected.
notifications.event_types empty = all none Allowlist of event types to deliver.
notifications.min_interval_seconds 60 DTM_NOTIFICATIONS_MIN_INTERVAL_SECONDS Per-source flap-dedupe window; repeats inside it are suppressed (and counted).

The observability guide and tag-based VM registration cover these in context.

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 size cap (oldest entries pruned).
observability.audit_retention disabled none Time-based audit retention as a duration, for example "2160h" for 90 days.
observability.otlp.* disabled none OTLP metrics export (endpoint, headers, interval_seconds, service_name, insecure); prefer PUT /api/v1/observability/otlp.
azure.subscription_id empty DTM_AZURE_SUBSCRIPTION_ID Subscription for the Azure integrations.
azure.monitor_enabled (+ monitor_resource_id, monitor_region) false none Push metrics to Azure Monitor.
azure.log_analytics_workspace_id / _key empty none Ship query logs to Log Analytics (requires azure.monitor_enabled).
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; keys and workflow in that guide.

Entra ID single sign-on covers setup end to end.

Key Default Env override What it does
entra.tenant_id empty none Your tenant. SSO activates when tenant, client, and secret are all set.
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.
entra.redirect_uri empty none The OAuth redirect URI registered on the app.
Key Default Env override What it does
allow_script_health_checks false none Allow script health-check probes. See the caution below before enabling.
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 empty none CIDRs allowed to set X-Forwarded-For. Deliberately file-only: when empty, forwarded headers are ignored and the peer address is used.
axfr.enabled false none Allow zone transfers (AXFR).
axfr.allowed_cidrs empty (all, when enabled) none Restrict transfers to these networks.
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.
forwarder_hardening.per_client_qps / per_client_burst off none Per-source-IP cap on forwarded queries.
cors_origin empty (no CORS) DTM_CORS_ORIGIN Access-Control-Allow-Origin value for the API.
Key Default Env override What it does
cache.enabled false (true as deployed) DTM_CACHE_ENABLED Cache forwarded responses.
cache.max_size 10000 (50000 as deployed) DTM_CACHE_MAX_SIZE Maximum cached entries.
cache.ttl_seconds 0 (use record TTL) none Cap cached-answer TTLs.
health_workers 16 DTM_HEALTH_WORKERS Health-probe worker pool size; raise it if very many checks saturate probing.
cross_region_poll_interval_seconds 30 DTM_CROSS_REGION_POLL_INTERVAL_SECONDS How often cross-region failover state is re-evaluated.
readyz.sync_timeout_action block none Whether a joining node that misses its first full sync keeps waiting (block) or serves anyway (proceed).
readyz.sync_timeout_seconds 10 none How long a joining node waits for that first sync.
readyz.drain_grace_seconds 10 none Pause after readiness flips to draining, so load balancers evict the node first.
memory.self_restart_bytes 0 (disabled) none Memory ceiling at which the service restarts itself cleanly rather than risking the OOM killer.

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.