Skip to content

Latest commit

Β 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

☁️ Azure Traffic Manager Profile Terraform Module

A standalone module that provisions a hardened Azure Traffic Manager profile β€” a global, DNS-based traffic load balancer with encrypted endpoint health monitoring β€” targeting hashicorp/azurerm ~> 4.0.

Terraform azurerm Module Version Type Resources


🧩 Overview

  • 🌐 Provisions one azurerm_traffic_manager_profile β€” a global DNS traffic director that answers queries at <relative_name>.trafficmanager.net.
  • 🧭 Supports every routing method the provider exposes: Priority (failover), Weighted, Performance, Geographic, MultiValue, and Subnet.
  • 🩺 Configures endpoint health monitoring (monitor_config) that decides which endpoints receive traffic β€” the profile's core resilience and failover control.
  • πŸ” Defaults health checks to HTTPS on port 443 so probes are encrypted out of the box.
  • 🧱 Leaves endpoints to their own catalog modules; this profile is attached to Azure / external / nested endpoints by exporting its id and fqdn.

πŸ’‘ Why it matters: Traffic Manager is DNS-level global load balancing. It never proxies your traffic β€” it hands clients the DNS answer for a healthy endpoint. The profile's health monitor is therefore the whole ballgame for failover: if the monitor is weak or unencrypted, so is your resilience story. This module makes the encrypted, sensible-default monitor the path of least resistance.

❀️ Support this project

If this module saves you time, here are three no-cost ways to help it reach other engineers:

πŸ—ΊοΈ Where this fits in the family

flowchart LR
  rg["terraform-azurerm-resource-group"]
  tmp["terraform-azurerm-traffic-manager-profile"]
  azep["terraform-azurerm-traffic-manager-azure-endpoint"]
  exep["terraform-azurerm-traffic-manager-external-endpoint"]
  nsep["terraform-azurerm-traffic-manager-nested-endpoint"]
  pip["terraform-azurerm-public-ip"]
  web["terraform-azurerm-linux-web-app"]

  rg -->|"resource_group_name"| tmp
  tmp -->|"profile id + fqdn"| azep
  tmp -->|"profile id + fqdn"| exep
  tmp -->|"profile id + fqdn"| nsep
  pip -->|"target public IP"| azep
  web -->|"target resource id"| azep

  classDef self fill:#0078D4,stroke:#004578,color:#ffffff;
  classDef keystone fill:#004578,stroke:#001d33,color:#ffffff;
  classDef sibling fill:#eef3f8,stroke:#b7c7d8,color:#1b2733;

  class tmp self;
  class azep,exep,nsep keystone;
  class rg,pip,web sibling;
Loading

The profile is the anchor: it consumes a resource group by name and emits an id + fqdn that the endpoint modules attach to. The endpoints, in turn, point at concrete backends (public IPs, web apps, external FQDNs).

🧬 What this module builds

flowchart TD
  client["DNS client query"]
  subgraph module["module: azurerm_traffic_manager_profile.this"]
    profile["azurerm_traffic_manager_profile (this)"]
    routing["traffic_routing_method: Priority / Weighted / Performance / Geographic / MultiValue / Subnet"]
    monitor["monitor_config: HTTPS probe on 443"]
  end
  ep1["endpoint A (sibling module)"]
  ep2["endpoint B (sibling module)"]
  b1["backend: public IP / web app"]
  b2["backend: external FQDN"]

  client -->|"resolves the trafficmanager.net FQDN"| profile
  profile -->|"applies routing method"| routing
  monitor -->|"health probe result"| profile
  routing -->|"returns healthy endpoint"| ep1
  routing -->|"failover / distribution"| ep2
  ep1 -->|"points at"| b1
  ep2 -->|"points at"| b2
  monitor -.->|"probes"| ep1
  monitor -.->|"probes"| ep2

  classDef self fill:#0078D4,stroke:#004578,color:#ffffff;
  classDef keystone fill:#004578,stroke:#001d33,color:#ffffff;
  classDef sibling fill:#eef3f8,stroke:#b7c7d8,color:#1b2733;

  class profile,routing,monitor self;
  class ep1,ep2 keystone;
  class client,b1,b2 sibling;
Loading

Resource inventory

Resource Count Role
azurerm_traffic_manager_profile.this 1 The keystone profile: DNS config, routing method, and health monitor.
dns_config block 1 Publishes the *.trafficmanager.net name and its TTL.
monitor_config block 1 The health probe applied to every endpoint.
custom_header block(s) 0..N Optional probe request headers, rendered from a keyed map.

βœ… Provider / Versions

Requirement Value
Terraform >= 1.12.0
azurerm provider ~> 4.0
Provider block None in this module β€” the caller configures provider "azurerm" { features {} }, auth, and subscription.

Schema notes that bite (verified against the live provider schema):

  • πŸ” name and resource_group_name are immutable β€” changing either forces a new profile.
  • πŸ” dns_config.relative_name is immutable β€” changing it forces replacement, because the published *.trafficmanager.net name changes.
  • ⚠️ max_return is only valid when traffic_routing_method = "MultiValue", where it is required (1–8). Setting it under any other method is rejected.
  • ⚠️ A monitor_config.path is only valid for HTTP/HTTPS probes. This module nulls it automatically when protocol = "TCP".
  • ⚠️ monitor_config.interval_in_seconds accepts only 10 or 30; a 10-second (fast) probe interval restricts the legal timeout_in_seconds / tolerated_number_of_failures combinations.
  • ℹ️ fqdn is computed and only known after apply.

πŸ”‘ Required Azure RBAC Roles / Permissions

  • Network Contributor on the target resource group, or a custom role granting Microsoft.Network/trafficManagerProfiles/* at the smallest scope that works (the resource group).
  • No data-plane permissions are needed β€” Traffic Manager is a control-plane, DNS-only service.

Azure Prerequisites

  • An existing resource group (Traffic Manager is global, but the profile is still managed inside a resource group).
  • The Microsoft.Network resource provider registered on the target subscription.
  • A dns_config.relative_name that is globally unique across *.trafficmanager.net.
  • The caller configures the provider "azurerm" { features {} } block, authentication, and subscription.

πŸ“ Module Structure

terraform-azurerm-traffic-manager-profile/
β”œβ”€β”€ providers.tf   # required_version >= 1.12.0; azurerm ~> 4.0; no provider block
β”œβ”€β”€ variables.tf   # deeply-typed object() schemas + tags/timeouts tail
β”œβ”€β”€ main.tf        # the single keystone azurerm_traffic_manager_profile.this
β”œβ”€β”€ outputs.tf     # id first, then name, then fqdn and other computed attributes
β”œβ”€β”€ README.md      # this document
β”œβ”€β”€ SCOPE.md       # the cross-module contract
β”œβ”€β”€ LICENSE        # MIT, Copyright (c) 2026 Casey Wood
└── .gitignore     # canonical library ignore set

βš™οΈ Quick Start

The smallest real call. Health monitoring defaults to encrypted HTTPS on port 443; you only supply identity, routing method, and the DNS label.

module "traffic_manager" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-traffic-manager-profile.git?ref=v1.0.0"

  name                   = "tmp-app-prod"
  resource_group_name    = "rg-networking-prod"
  traffic_routing_method = "Priority"

  dns_config = {
    relative_name = "contoso-app-prod" # -> contoso-app-prod.trafficmanager.net
  }

  tags = {
    environment = "prod"
    workload    = "web"
  }
}

ℹ️ The caller configures the provider, authentication, subscription, and the mandatory provider "azurerm" { features {} } block. This module intentionally declares none of them.

πŸ”Œ Cross-Module Contract

Consumes

Input Type Source module
resource_group_name string terraform-azurerm-resource-group (name)

Emits

Output Description Consumed by
id Traffic Manager profile Resource ID (first) terraform-azurerm-traffic-manager-azure-endpoint, -external-endpoint, -nested-endpoint (as parent or child), diagnostics, RBAC
name Profile name diagnostics / tagging
fqdn Public DNS name (<relative_name>.trafficmanager.net) CNAME records, endpoint modules
resource_group_name The resource group holding the profile downstream compositions
traffic_routing_method The active routing method documentation / downstream logic

πŸ“š Example Library

Values these examples reference but do not create are declared inputs:

variable "service_plan_id" {
  description = "service plan id of an existing resource these examples reference."
  type        = string
}
1 Β· Minimal priority (failover) profile
module "tmp" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-traffic-manager-profile.git?ref=v1.0.0"

  name                   = "tmp-failover"
  resource_group_name    = "rg-networking-prod"
  traffic_routing_method = "Priority"

  dns_config = {
    relative_name = "contoso-failover"
  }
}

πŸ’‘ Priority routing sends all traffic to the highest-priority healthy endpoint and fails over only when it goes unhealthy. The HTTPS-on-443 default monitor decides "healthy."

2 Β· Weighted routing
module "tmp" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-traffic-manager-profile.git?ref=v1.0.0"

  name                   = "tmp-weighted"
  resource_group_name    = "rg-networking-prod"
  traffic_routing_method = "Weighted"

  dns_config = {
    relative_name = "contoso-weighted"
    ttl           = 30
  }
}

πŸ’‘ Weight is set per endpoint (in the endpoint modules), not on the profile. A lower ttl lets clients pick up weight changes sooner.

3 Β· Performance routing
module "tmp" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-traffic-manager-profile.git?ref=v1.0.0"

  name                   = "tmp-performance"
  resource_group_name    = "rg-networking-prod"
  traffic_routing_method = "Performance"

  dns_config = {
    relative_name = "contoso-perf"
  }
}

πŸ’‘ Performance routing returns the endpoint with the lowest network latency for the querying resolver's region β€” ideal for multi-region active/active apps.

4 Β· Geographic routing
module "tmp" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-traffic-manager-profile.git?ref=v1.0.0"

  name                   = "tmp-geo"
  resource_group_name    = "rg-networking-prod"
  traffic_routing_method = "Geographic"

  dns_config = {
    relative_name = "contoso-geo"
  }
}

ℹ️ Geographic routing maps the query's origin region to an endpoint. Assign the geographic regions on the endpoints themselves (in the endpoint modules); each region maps to exactly one endpoint.

5 Β· Subnet routing
module "tmp" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-traffic-manager-profile.git?ref=v1.0.0"

  name                   = "tmp-subnet"
  resource_group_name    = "rg-networking-prod"
  traffic_routing_method = "Subnet"

  dns_config = {
    relative_name = "contoso-subnet"
  }
}

ℹ️ Subnet routing maps client source-IP ranges to specific endpoints (for example, sending corporate egress ranges to an internal endpoint). The IP-range-to-endpoint mapping is set on the endpoints.

6 Β· MultiValue routing with max_return
module "tmp" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-traffic-manager-profile.git?ref=v1.0.0"

  name                   = "tmp-multivalue"
  resource_group_name    = "rg-networking-prod"
  traffic_routing_method = "MultiValue"
  max_return             = 4

  dns_config = {
    relative_name = "contoso-mv"
  }
}

⚠️ max_return (1–8) is required for MultiValue and rejected for every other method. MultiValue returns several healthy endpoints in one DNS answer so the client can retry across them.

7 Β· HTTPS monitor with expected status ranges
module "tmp" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-traffic-manager-profile.git?ref=v1.0.0"

  name                   = "tmp-health"
  resource_group_name    = "rg-networking-prod"
  traffic_routing_method = "Priority"

  dns_config = {
    relative_name = "contoso-health"
  }

  monitor_config = {
    protocol                    = "HTTPS"
    port                        = 443
    path                        = "/healthz"
    expected_status_code_ranges = ["200-202", "301-301"]
  }
}

πŸ”’ Encrypted probe on a dedicated health path, treating 200-202 and a 301 redirect as healthy. Endpoints failing the range are pulled from DNS rotation.

8 Β· HTTPS monitor with custom headers (keyed map)
module "tmp" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-traffic-manager-profile.git?ref=v1.0.0"

  name                   = "tmp-headers"
  resource_group_name    = "rg-networking-prod"
  traffic_routing_method = "Performance"

  dns_config = {
    relative_name = "contoso-headers"
  }

  monitor_config = {
    protocol = "HTTPS"
    port     = 443
    path     = "/healthz"

    custom_header = {
      host  = { name = "Host", value = "app.contoso.com" }
      probe = { name = "X-Health-Probe", value = "traffic-manager" }
    }
  }
}

πŸ’‘ custom_header is a keyed map so each header is addressable and stable across plans. The Host header is the common case for probing multi-tenant front ends.

9 Β· Fast-failover monitor (10-second interval)
module "tmp" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-traffic-manager-profile.git?ref=v1.0.0"

  name                   = "tmp-fast"
  resource_group_name    = "rg-networking-prod"
  traffic_routing_method = "Priority"

  dns_config = {
    relative_name = "contoso-fast"
    ttl           = 10
  }

  monitor_config = {
    protocol                     = "HTTPS"
    port                         = 443
    path                         = "/healthz"
    interval_in_seconds          = 10
    timeout_in_seconds           = 9
    tolerated_number_of_failures = 3
  }
}

⚠️ Fast probing (interval_in_seconds = 10) shortens failover time but constrains the legal timeout_in_seconds and tolerated_number_of_failures combinations. Pair it with a low DNS ttl.

10 Β· TCP monitor (path is nulled automatically)
module "tmp" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-traffic-manager-profile.git?ref=v1.0.0"

  name                   = "tmp-tcp"
  resource_group_name    = "rg-networking-prod"
  traffic_routing_method = "Priority"

  dns_config = {
    relative_name = "contoso-tcp"
  }

  monitor_config = {
    protocol = "TCP"
    port     = 443
  }
}

ℹ️ For a raw TCP handshake probe, leave path unset β€” the module nulls it automatically when protocol = "TCP", so no invalid TCP path is ever sent. Prefer HTTPS where the endpoint speaks HTTP.

11 Β· Disabled profile (staged, not yet live)
module "tmp" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-traffic-manager-profile.git?ref=v1.0.0"

  name                   = "tmp-staged"
  resource_group_name    = "rg-networking-prod"
  traffic_routing_method = "Priority"
  profile_status         = "Disabled"

  dns_config = {
    relative_name = "contoso-staged"
  }
}

⚠️ A Disabled profile answers no DNS queries β€” every endpoint is out of rotation. Use it to stage a profile before cut-over, then flip to Enabled.

12 Β· Traffic View enabled (latency telemetry)
module "tmp" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-traffic-manager-profile.git?ref=v1.0.0"

  name                   = "tmp-insights"
  resource_group_name    = "rg-networking-prod"
  traffic_routing_method = "Performance"
  traffic_view_enabled   = true

  dns_config = {
    relative_name = "contoso-insights"
  }
}

ℹ️ Traffic View aggregates client-network telemetry into latency insights and incurs additional cost. It is off by default; enable it only where you will use the data.

13 Β· Fully-specified, hardened profile with tags and timeouts
module "tmp" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-traffic-manager-profile.git?ref=v1.0.0"

  name                   = "tmp-web-prod"
  resource_group_name    = "rg-networking-prod"
  traffic_routing_method = "Priority"
  profile_status         = "Enabled"

  dns_config = {
    relative_name = "contoso-web-prod"
    ttl           = 30
  }

  monitor_config = {
    protocol                     = "HTTPS"
    port                         = 443
    path                         = "/healthz"
    interval_in_seconds          = 30
    timeout_in_seconds           = 10
    tolerated_number_of_failures = 3
    expected_status_code_ranges  = ["200-299"]

    custom_header = {
      host = { name = "Host", value = "app.contoso.com" }
    }
  }

  tags = {
    environment = "prod"
    workload    = "web"
    owner       = "platform-team"
  }

  timeouts = {
    create = "30m"
    delete = "30m"
  }
}

πŸ”’ Everything explicit: encrypted probe, dedicated health path, status-range gating, and a Host header β€” a production-grade baseline.

14 Β· for_each at scale β€” one profile per workload
locals {
  profiles = {
    web  = { relative_name = "contoso-web", routing = "Performance" }
    api  = { relative_name = "contoso-api", routing = "Priority" }
    edge = { relative_name = "contoso-edge", routing = "Weighted" }
  }
}

module "tmp" {
  source   = "git::https://github.com/microsoftexpert/terraform-azurerm-traffic-manager-profile.git?ref=v1.0.0"
  for_each = local.profiles

  name                   = "tmp-${each.key}"
  resource_group_name    = "rg-networking-prod"
  traffic_routing_method = each.value.routing

  dns_config = {
    relative_name = each.value.relative_name
  }

  tags = { workload = each.key }
}

πŸ’‘ One module block, many profiles. Each keeps its own routing method and DNS label; the shared HTTPS monitor default applies to all.

15 Β· πŸ—οΈ End-to-end composition (resource group + profile + Azure endpoints)

Wires real sibling module outputs into inputs: a resource group holds the profile, and two traffic-manager-azure-endpoint sibling modules attach to the profile by id and point at a public IP and a Linux web app.

module "rg" {
  source   = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"
  name     = "rg-networking-prod"
  location = "eastus"
}

module "pip_primary" {
  source              = "git::https://github.com/microsoftexpert/terraform-azurerm-public-ip.git?ref=v1.0.0"
  name                = "pip-web-primary"
  resource_group_name = module.rg.name
  location            = "eastus"
}

module "web_secondary" {
  source              = "git::https://github.com/microsoftexpert/terraform-azurerm-linux-web-app.git?ref=v1.0.0"

  service_plan_id              = var.service_plan_id
  name                = "app-web-secondary"
  resource_group_name = module.rg.name
  location            = "westus2"
}

module "traffic_manager" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-traffic-manager-profile.git?ref=v1.0.0"

  name                   = "tmp-web-prod"
  resource_group_name    = module.rg.name
  traffic_routing_method = "Priority"

  dns_config = {
    relative_name = "contoso-web-prod"
    ttl           = 30
  }

  monitor_config = {
    protocol                    = "HTTPS"
    port                        = 443
    path                        = "/healthz"
    expected_status_code_ranges = ["200-299"]
    custom_header               = { host = { name = "Host", value = "app.contoso.com" } }
  }

  tags = { environment = "prod", workload = "web" }
}

# Primary endpoint β€” highest priority, targets the public IP.
module "endpoint_primary" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-traffic-manager-azure-endpoint.git?ref=v1.0.0"

  name                   = "primary"
  profile_id             = module.traffic_manager.id
  target_resource_id     = module.pip_primary.id
  priority               = 1
}

# Secondary endpoint β€” failover target, the web app.
module "endpoint_secondary" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-traffic-manager-azure-endpoint.git?ref=v1.0.0"

  name               = "secondary"
  profile_id         = module.traffic_manager.id
  target_resource_id = module.web_secondary.id
  priority           = 2
}

output "traffic_manager_fqdn" {
  value = module.traffic_manager.fqdn
}

πŸ’‘ The profile owns routing and health monitoring; the endpoint modules own the backends and their per-endpoint priority/weight. Clients resolve contoso-web-prod.trafficmanager.net and receive the primary endpoint until its HTTPS probe fails, then the secondary.

πŸ“₯ Inputs

Identity (required) β€” name, resource_group_name. Routing (required) β€” traffic_routing_method, dns_config (with relative_name). Health & behavior (optional, secure defaults) β€” monitor_config, profile_status, max_return, traffic_view_enabled. Universal tail β€” tags, timeouts.

Full object() schemas
Name Type Default Description
name string β€” (required) Profile resource name. Immutable / force-new.
resource_group_name string β€” (required) Existing resource group name. Immutable / force-new.
traffic_routing_method string β€” (required) Priority | Weighted | Performance | Geographic | MultiValue | Subnet. Updatable in place.
dns_config object({ relative_name = string, ttl = optional(number, 60) }) β€” (required) Publishes <relative_name>.trafficmanager.net. relative_name is force-new.
monitor_config object({ protocol = optional(string,"HTTPS"), port = optional(number,443), path = optional(string,"/"), interval_in_seconds = optional(number,30), timeout_in_seconds = optional(number,10), tolerated_number_of_failures = optional(number,3), expected_status_code_ranges = optional(list(string)), custom_header = optional(map(object({ name = string, value = string })), {}) }) HTTPS/443 probe Endpoint health monitor. path is auto-nulled for TCP.
profile_status string "Enabled" Enabled | Disabled.
max_return number null 1–8; required for MultiValue, rejected otherwise.
traffic_view_enabled bool false Latency telemetry (extra cost).
tags map(string) {} Tags applied to the profile.
timeouts object({ create, read, update, delete = optional(string) }) null Optional operation timeouts.

🧾 Outputs

Output Description Kind
id The Azure Resource ID of the Traffic Manager profile Passthrough
name The name of the Traffic Manager profile Passthrough
fqdn The fully-qualified DNS name of the profile (e.g Passthrough
resource_group_name The resource group that holds the profile Passthrough
traffic_routing_method The routing method the profile applies to DNS queries Passthrough
dns_relative_name The DNS label the profile answers on, the leftmost part of fqdn Derived
dns_ttl_seconds The DNS time-to-live in seconds returned to resolvers Derived
failover_detection_seconds How long Traffic Manager takes to decide an endpoint is unhealthy, in seconds: interval_in_seconds x (tolerated_number_of_failures + 1) Derived
worst_case_failover_seconds The upper bound on how long clients may keep reaching a failed endpoint, in seconds: detection time plus the DNS TTL Passthrough
dns_ttl_dominates_failover_time True when the DNS TTL is longer than the health monitor's detection time, meaning DNS caching - not probing - is what limits failover speed Derived
monitor_protocol The protocol used for health probes Derived
monitor_port The port health probes connect to Derived
probe_is_encrypted True when health probes run over HTTPS Derived
probe_path_is_ignored True when the probe protocol is TCP, where the path has no effect Derived
fast_probing_enabled True when interval_in_seconds is 10 rather than 30 Derived
single_probe_failure_removes_endpoint True when tolerated_number_of_failures is 0, so one missed probe takes an endpoint out of rotation Derived
endpoint_weight_is_ignored_by_this_routing_method True unless the routing method is Weighted Derived
endpoint_priority_is_ignored_by_this_routing_method True unless the routing method is Priority Derived
endpoint_geo_mappings_are_required_by_this_routing_method True when the routing method is Geographic, where an endpoint with no geo_mappings receives no traffic at all rather than acting as a default Derived
endpoint_subnets_are_required_by_this_routing_method True when the routing method is Subnet, where a client address matching no endpoint's ranges gets no answer Derived
endpoint_location_is_required_by_this_routing_method True when the routing method is Performance, which needs an endpoint_location on each external and nested endpoint Derived
traffic_view_is_billed_separately True when Traffic View is enabled Derived
profile_answers_no_queries True when profile_status is Disabled Derived
profile_status Whether the profile is Enabled or Disabled Passthrough
max_return The maximum number of endpoints returned in one answer, or null Passthrough

🧠 Architecture Notes

  • The health monitor is the resilience control. Traffic Manager routes only to endpoints its monitor_config reports healthy. Because the module defaults to HTTPS on 443, the empty call already performs encrypted health checks; loosening to TCP or HTTP is an explicit caller choice.
  • Force-new fields bite hard. name, resource_group_name, and dns_config.relative_name are all immutable. Changing relative_name in particular republishes the profile under a new DNS name and forces replacement β€” treat it as permanent once endpoints and CNAMEs depend on it.
  • max_return is method-coupled. It is required for MultiValue and rejected everywhere else; the module keeps it null by default and validates the 1–8 range so a stray value fails at plan time rather than deep in an apply.
  • path is protocol-coupled. The provider rejects a path on TCP probes; main.tf nulls path whenever protocol = "TCP", so a caller cannot construct that invalid combination through this module.
  • custom_header key stability. Probe headers are a keyed map rendered through a dynamic block, so adding or removing one header never re-indexes the others.
  • features {} dependence. The profile relies on the caller's provider "azurerm" { features {} } block; the module declares no provider block by design.

🧱 Design Principles

Secure by default β€” the empty call already produces the hardened resource; each relaxation is an explicit opt-out the caller must type.

Concern Secure default (empty call) Opt-out (caller types it)
Health-probe encryption monitor_config.protocol = "HTTPS" on port = 443 set protocol = "TCP" / "HTTP" or a different port
Profile availability profile_status = "Enabled" (predictable, live behavior) set profile_status = "Disabled"
Probe path validity path auto-nulled when protocol = "TCP" supply HTTP/HTTPS with an explicit path
Telemetry cost/exposure traffic_view_enabled = false set traffic_view_enabled = true
Method-coupled inputs max_return = null unless MultiValue set max_return (1–8) with MultiValue
DNS TTL ttl = 60 (balanced failover vs. query volume) raise or lower dns_config.ttl

πŸš€ Runbook

# From the module folder β€” plan-only; a human applies from CI.
terraform init -backend=false
terraform validate
terraform fmt -check
  • Pin the source to a tag β€” ?ref=v1.0.0 β€” never a branch.
  • No apply happens during authoring; a human runs terraform apply from CI against real credentials.

πŸ§ͺ Testing

The offline proof gate is what this repository guarantees:

  • terraform validate proves the configuration is internally consistent and type-correct against the pinned provider schema β€” every object() shape, enum validation {}, and reference is checked without calling Azure.
  • terraform fmt -check enforces canonical formatting.
  • Only terraform plan (run by a human, against credentials, from CI) exercises the ARM API and confirms live behavior such as DNS-name uniqueness and method-coupled field acceptance. This repository never runs plan or apply during authoring.

πŸ’¬ Example Output

$ terraform output
id                     = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-networking-prod/providers/Microsoft.Network/trafficManagerProfiles/tmp-web-prod"
name                   = "tmp-web-prod"
fqdn                   = "contoso-web-prod.trafficmanager.net"
resource_group_name    = "rg-networking-prod"
traffic_routing_method = "Priority"

πŸ” Troubleshooting

Symptom Cause Fix
max_return error at plan/apply max_return set with a non-MultiValue method, or missing for MultiValue Set max_return (1–8) only when traffic_routing_method = "MultiValue"; leave it null otherwise.
Profile answers no DNS queries profile_status = "Disabled" Set profile_status = "Enabled".
All endpoints show unhealthy HTTPS probe on 443 fails (no TLS listener, wrong path, or status outside the expected range) Match monitor_config.protocol/port/path to the endpoint, or widen expected_status_code_ranges.
"path is not valid for TCP" style rejection A path reached a TCP probe Use this module's default handling (leave path unset for TCP) or switch to HTTPS.
Plan wants to replace the profile Changed name, resource_group_name, or dns_config.relative_name (all force-new) Keep them stable; treat relative_name as permanent once endpoints/CNAMEs depend on it.
provider not initialized / features error Caller root module missing provider "azurerm" { features {} } Add the features {} block in the root module; this module intentionally omits it.
DNS name already taken relative_name collides in the global *.trafficmanager.net namespace Choose a globally-unique relative_name.

πŸ”— Related Docs

  • Terraform Registry β€” azurerm_traffic_manager_profile
  • Microsoft Learn β€” Azure Traffic Manager documentation
  • Sibling modules β€” terraform-azurerm-traffic-manager-azure-endpoint, terraform-azurerm-traffic-manager-external-endpoint, terraform-azurerm-traffic-manager-nested-endpoint, terraform-azurerm-resource-group, terraform-azurerm-public-ip.
  • This module's SCOPE.md β€” the cross-module contract.

πŸ’™ "Infrastructure as Code should be standardized, consistent, and secure."