Skip to content

Health checks and load balancing with Terraform

Two nested blocks turn a plain record into a traffic-managed one: lb_policy decides which healthy values to return, and health_check_template decides how DTM probes them. Both attach to dtm_record, dtm_override_record, and dtm_auto_record (auto records reject value_weights). lb_policy can also be set on dtm_zone as the zone-wide default that records without their own policy inherit. There is also a record-independent dtm_healthcheck resource (monitor-only).

lb_policy {
method = "region"
}
Attribute Description
method round-robin, failover, latency, region, geo-failover, or weighted. See load-balancing methods.
client_affinity Pin a client (by source-IP hash) to one value.
affinity_ttl How long (seconds) the client-to-value mapping holds, up to a 24 hour maximum (larger values are clamped). Only meaningful with client_affinity = true.
failover_order Explicit ordering for method = "failover". Values not listed fall to the end.
region_preference Region preference order, honoured only when method = "region". It is accepted but ignored under geo-failover.
value_weights For method = "weighted": map of record value to a percentage (0 to 100; listed weights may sum to at most 100). Unlisted values split the leftover evenly; 0 drains a value. Works on dtm_record and dtm_override_record.
lb_policy {
method = "weighted"
value_weights = {
"10.0.1.10" = 90 # current
"10.0.1.20" = 10 # canary
}
}

Set a low TTL on the record: the split is applied per DNS answer, so resolver caching smears it unless answers expire quickly. See known limitations.

lb_policy {
method = "failover"
failover_order = ["10.0.1.10", "10.0.1.11", "10.0.2.10"]
}
lb_policy {
method = "region"
region_preference = ["northeurope", "westeurope"]
client_affinity = true
affinity_ttl = 300
}

When set, DTM creates one health check per value from this template and keeps them in sync as values change.

health_check_template {
protocol = "https"
port = 443
path = "/healthz"
interval_seconds = 15
timeout_seconds = 5
expected_status = [200]
}
Attribute Description
protocol tcp, http, or https.
port Target port (1-65535).
path HTTP/HTTPS path, e.g. /healthz.
interval_seconds Probe interval. Default 15, minimum 5.
timeout_seconds Per-probe timeout.
tls_skip_verify Skip TLS verification (self-signed HTTPS targets).
expected_status HTTP status codes treated as healthy (default [200]).
expected_body Substring the response body must contain.
headers Static request headers sent on every probe (e.g. an Authorization bearer). Header values are secrets: the API returns them redacted, and the provider keeps the values you applied in state.
ca_pem PEM CA bundle that verifies the target’s TLS certificate (server-cert pin).

See health checks for tuning guidance on interval and thresholds.

For auth-gated backends, add one of the oauth2, mtls, or managed_identity sub-blocks (see authenticated health probes for the behaviour). DTM requires HTTPS with verified TLS when credentials are attached: use protocol = "https" and set ca_pem or leave tls_skip_verify off. A template carrying one of those three sub-blocks over plain http is rejected. A static headers token is not covered by that check, so set protocol = "https" for those too. (An admin can relax the certificate-verification half for the node the provider endpoint targets with the dtm_health_probe_policy_config singleton resource; apply it through each node’s endpoint to cover the cluster. The HTTPS requirement always stands.)

# OAuth2 client-credentials
health_check_template {
protocol = "https"
port = 443
path = "/healthz"
interval_seconds = 15
oauth2 {
token_url = "https://login.example.com/oauth2/token"
client_id = var.probe_client_id
client_secret = var.probe_client_secret # sensitive
scope = "health.read"
}
}
# Azure Managed Identity - no stored credentials
health_check_template {
protocol = "https"
port = 443
path = "/healthz"
interval_seconds = 15
managed_identity {
resource = "api://<app-id-uri>"
# client_id = "<user-assigned-mi-client-id>" # omit for the VM's system-assigned identity
}
}
# Mutual TLS
health_check_template {
protocol = "https"
port = 443
path = "/healthz"
interval_seconds = 15
mtls {
cert_pem = file("client.pem")
key_pem = file("client.key") # sensitive
}
}

Probe secrets are write-only through the API: reads return client_secret, key_pem, and all headers values redacted as ***. The provider preserves the values you applied in state, so a refresh or plan shows no spurious secret drift. To rotate a secret, change the value in your configuration and apply as normal. The one exception is terraform import: an imported resource has no previously applied value, so the redacted *** placeholder sits in state (and shows as a diff) until your first apply restores the real values.

dtm_healthcheck (role: editor or admin) manages a health check independently of any record. It is monitor-only: the check probes its target and reports status (the Health Checks page and the dtm_health_check_status metric) but never gates a record’s DNS answers. To make answers health-aware, use a health_check_template on the record, as above.

resource "dtm_healthcheck" "web" {
name = "web-http"
protocol = "http"
target = "10.0.1.10:80"
interval_seconds = 15
timeout_seconds = 5
failure_threshold = 3
success_threshold = 2
http_config {
method = "GET"
path = "/healthz"
expected_status = [200]
}
}
Attribute Required Description
name Yes Human-readable name.
protocol Yes tcp, http, or https.
target Yes host:port or a bare IP, for every protocol. No scheme and no path: for http/https the scheme comes from protocol and the request path from http_config.path.
interval_seconds Yes Probe interval (minimum 5).
timeout_seconds Yes Per-probe timeout.
failure_threshold Yes Consecutive failures before unhealthy.
success_threshold Yes Consecutive successes before healthy again.
region_id No Region tag for region-aware filtering.
failure_rate_window_seconds No Sliding window for rate-based failure detection; 0 (default) disables it. If you set it, you must also set failure_rate_percent.
failure_rate_percent No Percentage of failed probes within the window that flips the check unhealthy; must be set together with a non-zero failure_rate_window_seconds, and supplying only one of the pair is rejected with a 400.
cross_region No Allow the check result to drive failover across regions; leave unset to accept the server-side default.
http_config (block) For HTTP/HTTPS method, path, expected_status, expected_body, headers, tls_skip_verify, ca_pem, and the oauth2 / mtls / managed_identity auth blocks. headers values are secrets: returned redacted by the API and preserved in state, exactly as for record probes above.

http_config carries the same authenticated-probe fields as the record template, field for field: ca_pem plus the oauth2, mtls, and managed_identity blocks. The rules from authenticated probes apply unchanged: at most one bearer method (oauth2 or managed_identity, with mtls composable with either), HTTPS with verified TLS whenever credentials are attached, and the same secret round-trip (redacted reads, state-preserved values, and the terraform import placeholder caveat).

resource "dtm_healthcheck" "secured_api" {
name = "secured-api"
protocol = "https"
target = "api.internal:443"
interval_seconds = 30
timeout_seconds = 3
failure_threshold = 3
success_threshold = 1
http_config {
method = "GET"
path = "/healthz"
expected_status = [200]
ca_pem = file("${path.module}/backend-ca.pem")
oauth2 {
token_url = "https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token"
client_id = "<app-id>"
client_secret = var.probe_client_secret # sensitive
scope = "api://<app-id>/.default"
}
}
}