Provisions a self-hosted API Management gateway with its API assignments, trusted certificate authorities, and host-name configurations — legacy TLS off by default. Targets
hashicorp/azurerm ~> 4.0.
- 🌉 The self-hosted gateway (
azurerm_api_management_gateway) with required physicallocation_data. - 🔗 API assignments (
..._gateway_api), 🔐 certificate authorities (..._gateway_certificate_authority), and 🌐 host-name configurations (..._gateway_host_name_configuration) — keyed maps. - 🔒 Host-name configs disable TLS 1.0/1.1 by default.
💡 Why it matters: Self-hosted gateways extend API Management to on-prem/edge. Their host-name configurations are a common place legacy TLS creeps back in; this module defaults TLS 1.0/1.1 off so a hardened listener is what you get without extra input.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
flowchart LR
apim["terraform-azurerm-<br/>api-management"]
gw["terraform-azurerm-<br/>api-management-gateway"]
ch["gateway api / cert-authority / host-name (children)"]
apim -->|"id (api_management_id)"| gw
gw -->|"id / name"| ch
classDef me fill:#0078D4,stroke:#004578,color:#ffffff;
classDef target fill:#004578,stroke:#002d4d,color:#ffffff;
classDef sib fill:#eef3f8,stroke:#b8c4d0,color:#1b1b1b;
class gw me;
class apim target;
class ch sib;
flowchart TD
core["name · api_management_id · location_data"]
this["azurerm_api_management_gateway.this"]
a["gateway_api (for_each)"]
c["certificate_authority (for_each)"]
h["host_name_configuration (for_each)"]
core --> this
this --> a
this --> c
this --> h
classDef me fill:#0078D4,stroke:#004578,color:#ffffff;
classDef child fill:#eef3f8,stroke:#b8c4d0,color:#1b1b1b;
class this me;
class a,c,h child;
Resource inventory
| Resource | Count | Role |
|---|---|---|
azurerm_api_management_gateway.this |
1 | The self-hosted gateway (keystone). |
azurerm_api_management_gateway_api.api |
0..N | API assignments (for_each). |
azurerm_api_management_gateway_certificate_authority.certificate_authority |
0..N | Trusted CAs (for_each). |
azurerm_api_management_gateway_host_name_configuration.host_name_configuration |
0..N | Host-name listeners (for_each). |
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
| Provider | hashicorp/azurerm ~> 4.0 |
| Provider block | None in this module — the caller configures provider "azurerm" { features {} }. |
Schema notes that bite (verified against the live provider schema):
- 🔒
nameandapi_management_idare effectively immutable. - 🧩 The gateway references the service by
id; CA and host-name children reference the service byidplus the gateway byname. - 🔐 TLS 1.0/1.1 default to disabled on host-name configurations.
- 🚫 No Azure resource
tags— the tail istimeoutsonly.
- Contributor on the API Management service's resource group (or a least-privilege role with write on
Microsoft.ApiManagement/service/gateways/*).
- An existing API Management service (the
api-managementmodule). - An API Management certificate (
azurerm_api_management_certificate) for any host-name configuration. It may itself reference a Key Vault secret, but a Key Vault ID cannot be passed here. - The caller configures the
provider "azurerm" { features {} }block, auth, and subscription.
terraform-azurerm-api-management-gateway/
├── providers.tf · variables.tf · main.tf · outputs.tf
├── README.md · SCOPE.md · LICENSE · .gitignore
provider "azurerm" {
features {}
}
module "apim" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management.git?ref=v1.0.0"
name = "apim-contoso-prod"
resource_group_name = "rg-apim-eastus"
location = "eastus"
publisher_name = "Contoso Platform"
publisher_email = "platform@contoso.example"
sku_name = "Developer_1"
}
module "orders_api" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-api.git?ref=v1.0.0"
name = "orders"
resource_group_name = "rg-apim-eastus"
api_management_name = module.apim.name
display_name = "Orders API"
path = "orders"
}
module "catalog_api" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-api.git?ref=v1.0.0"
name = "catalog"
resource_group_name = "rg-apim-eastus"
api_management_name = module.apim.name
display_name = "Catalog API"
path = "catalog"
}
module "edge_gateway" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
name = "edge-eastus"
api_management_id = module.apim.id
description = "On-prem edge gateway"
location_data = { name = "Contoso HQ", city = "Redmond", region = "WA" }
}Consumes
| Input | Type | Source module |
|---|---|---|
api_management_id |
string |
terraform-azurerm-api-management (its id) |
gateway_apis[*].api_id |
string |
terraform-azurerm-api-management-api (its id) |
host_name_configurations[*].certificate_id |
string |
terraform-azurerm-api-management-certificate output id — an API Management certificate ID (.../Microsoft.ApiManagement/service/<svc>/certificates/<name>), not a Key Vault ID |
Emits
| Output | Description |
|---|---|
id |
Gateway Resource ID (first). |
name |
Gateway name. |
gateway_api_ids / certificate_authority_ids / host_name_configuration_ids |
Maps of child key → Resource ID. |
1 · Minimal gateway
module "gw" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
name = "edge-eastus"
api_management_id = module.apim.id
location_data = {
name = "Contoso DC 1"
}
}2 · Full location metadata
module "gw" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
name = "edge-hq"
api_management_id = module.apim.id
location_data = { name = "Contoso HQ", city = "Redmond", district = "King", region = "WA" }
}3 · Assign an API to the gateway
module "gw" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
name = "edge-eastus"
api_management_id = module.apim.id
location_data = {
name = "Contoso DC 1"
}
gateway_apis = { orders = { api_id = module.orders_api.id } }
}4 · Assign several APIs
module "gw" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
name = "edge-eastus"
api_management_id = module.apim.id
location_data = {
name = "Contoso DC 1"
}
gateway_apis = {
orders = { api_id = module.orders_api.id }
catalog = { api_id = module.catalog_api.id }
}
}5 · Host-name configuration (hardened TLS)
module "gw" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
name = "edge-eastus"
api_management_id = module.apim.id
location_data = {
name = "Contoso DC 1"
}
host_name_configurations = {
api = {
host_name = "api.contoso.com"
certificate_id = var.gateway_cert_id
http2_enabled = true
# tls10_enabled / tls11_enabled default to false
}
}
}🔒 Legacy TLS stays off unless you explicitly set
tls10_enabled/tls11_enabled = true.
6 · Require client certificates (mTLS)
module "gw" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
name = "edge-eastus"
api_management_id = module.apim.id
location_data = {
name = "Contoso DC 1"
}
host_name_configurations = {
api = {
host_name = "api.contoso.com"
certificate_id = var.gateway_cert_id
request_client_certificate_enabled = true
}
}
}7 · Trusted certificate authority
module "gw" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
name = "edge-eastus"
api_management_id = module.apim.id
location_data = {
name = "Contoso DC 1"
}
certificate_authorities = { root = { is_trusted = true } }
}8 · Legacy TLS (explicit opt-out)
module "gw" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
name = "edge-legacy"
api_management_id = module.apim.id
location_data = {
name = "Contoso DC 1"
}
host_name_configurations = {
api = {
host_name = "legacy.contoso.com"
certificate_id = var.gateway_cert_id
tls11_enabled = true # ⚠️ re-enables a deprecated TLS version
}
}
}9 · Custom timeouts
module "gw" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
name = "edge-eastus"
api_management_id = module.apim.id
location_data = {
name = "Contoso DC 1"
}
timeouts = { create = "30m" }
}10 · Multiple host names
module "gw" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
name = "edge-eastus"
api_management_id = module.apim.id
location_data = {
name = "Contoso DC 1"
}
host_name_configurations = {
api = { host_name = "api.contoso.com", certificate_id = var.api_cert_id }
dev = { host_name = "dev.contoso.com", certificate_id = var.dev_cert_id }
}
}11 · for_each over multiple gateways
module "gw" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
for_each = toset(["edge-eastus", "edge-westus"])
name = each.key
api_management_id = module.apim.id
location_data = {
name = "Contoso DC 1"
}
}12 · 🏗️ End-to-end composition
provider "azurerm" {
features {}
}
module "gw" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-gateway.git?ref=v1.0.0"
name = "edge-eastus"
api_management_id = module.apim.id
location_data = {
name = "Contoso DC 1"
}
description = "Edge gateway for the orders estate"
gateway_apis = { orders = { api_id = module.orders_api.id } }
certificate_authorities = { root = { is_trusted = true } }
host_name_configurations = {
api = { host_name = "api.contoso.com", certificate_id = var.gateway_cert_id, http2_enabled = true }
}
}💡 The gateway references the APIM service
idand the APIid, so ordering is implicit.
Required: name, api_management_id.
Optional: description, location_data (object), gateway_apis (map), certificate_authorities (map), host_name_configurations (map), timeouts.
Full input schemas
variable "host_name_configurations" {
type = map(object({
host_name = string
certificate_id = string
http2_enabled = optional(bool)
request_client_certificate_enabled = optional(bool, false)
tls10_enabled = optional(bool, false)
tls11_enabled = optional(bool, false)
}))
default = {}
}
# gateway_apis = map({ api_id }); certificate_authorities = map({ is_trusted? }); see variables.tf.| Output | Description | Kind |
|---|---|---|
id |
Resource ID of the API Management gateway registration | Passthrough |
name |
Name of the gateway registration, as created | Passthrough |
api_management_id |
Resource ID of the parent API Management service | Passthrough |
api_management_name |
Name of the parent API Management service, taken from the service ID | Derived |
resource_group_name |
Resource group holding the parent API Management service, taken from the service ID | Derived |
description |
The gateway's description as Azure holds it | Passthrough |
location_data_name |
The canonical location name recorded on the gateway, as Azure holds it | Derived |
location_data |
The full location metadata block as Azure holds it, with name, city, district and region | Derived |
location_data_is_required_and_is_metadata_only |
Constant true, and a correction worth carrying | Constant |
location_data_region_looks_like_an_azure_region_code |
True when location_data.region was given a value shaped like an Azure region code -- "eastus", "westus2", "centralus" and the like | Derived |
region_is_sent_to_azure_as_country_or_region |
Constant true | Constant |
gateway_api_ids |
Map of API-assignment key to the Resource ID of the assignment record | Derived |
gateway_api_names |
Map of API-assignment key to the API NAME the provider will actually use, derived the way the provider derives it: the last segment of api_id, cut at the first semicolon | Derived |
gateway_api_assignments_ignore_the_service_in_api_id |
Constant true, and the reason this module validates api_id more strictly than the provider does | Constant |
removing_an_api_assignment_does_not_delete_the_api |
Constant true | Constant |
certificate_authority_ids |
Map of certificate-authority key to the Resource ID of the trust record | Derived |
trusted_certificate_authority_names |
Names of the certificate authorities this gateway actually TRUSTS -- the entries with is_trusted set to true | Derived |
registered_but_untrusted_certificate_authority_names |
Names of certificate authorities registered on this gateway with is_trusted false | Derived |
trusted_certificate_authorities_with_no_host_name_requesting_client_certificates |
True when at least one certificate authority is trusted but no host-name configuration sets request_client_certificate_enabled, which makes the whole trust list inert | Derived |
host_name_configuration_ids |
Map of host-name-configuration key to the Resource ID of the record | Derived |
host_names |
Map of host-name-configuration key to the host name this gateway answers on, as Azure holds it | Derived |
host_name_certificate_ids |
Map of host-name-configuration key to the API Management certificate Resource ID serving it, as Azure holds it | Derived |
host_name_configurations_permitting_legacy_tls |
Names of the host-name configurations that allow TLS 1.0 or TLS 1.1 | Derived |
host_name_configurations_requesting_client_certificates |
Names of the host-name configurations that negotiate a client certificate from callers | Derived |
host_name_certificates_in_another_api_management_service |
Names of any host-name configurations whose certificate_id sits under a DIFFERENT API Management service than this gateway's | Passthrough |
certificate_id_must_be_an_api_management_certificate |
Constant true, and a correction to a widely held assumption about this field | Constant |
creating_this_resource_deploys_no_gateway |
Constant true, and the first thing to understand about this module | Constant |
requires_developer_or_premium_tier |
Constant true | Constant |
no_deployment_token_is_emitted_by_this_module |
Constant true, and deliberate | Constant |
access_token_authentication_expires_within_30_days |
Constant true, and an operational obligation this resource creates without recording | Constant |
entra_workload_identity_removes_the_token_entirely |
Constant true, and this module's recommendation against the default path | Constant |
fields_azure_returns_on_read |
The gateway fields the provider repopulates from the Azure API on every read, and therefore the only fields in which drift can be detected on the keystone | Derived |
this_resource_supports_no_azure_resource_tags |
Constant true, for the gateway and for all three of its child resources | Constant |
nameandapi_management_idare the gateway's fixed identity.- Mixed reference styles: the gateway uses
api_management_id; the CA and host-name children useapi_management_id+ the gatewayname; the API assignment usesgateway_id(the keystoneid). - Secure TLS default: host-name configs disable TLS 1.0/1.1; enabling either is an explicit, reviewable change.
- No secrets, no
tags. Certificates are referenced by ID; the tail istimeoutsonly. features {}dependence — configured by the caller.
| Concern | Secure default | Opt-out |
|---|---|---|
| Legacy TLS 1.0 | tls10_enabled = false |
set true |
| Legacy TLS 1.1 | tls11_enabled = false |
set true |
| Client certificates | request_client_certificate_enabled = false |
set true (to require mTLS) |
| Secrets | none accepted or emitted | — |
Host-name listeners are hardened by default; relaxing TLS is explicit.
terraform init -backend=false
terraform validate
terraform fmt -check- Pin by immutable tag (
?ref=v1.0.0). Plan-only; a human applies from CI.
init -backend=false / validate / fmt -check confirm the config and schemas. Only plan/apply against a live service exercises the parent APIM existence, certificate validity, and gateway registration.
$ terraform output
id = "/subscriptions/0000.../service/apim-contoso/gateways/edge-eastus"
name = "edge-eastus"
host_name_configuration_ids = { "api" = "/subscriptions/0000.../gateways/edge-eastus/hostnameConfigurations/api" }| Symptom | Cause | Fix |
|---|---|---|
api_management_id must be the full Resource ID of an API Management SERVICE and nothing deeper... |
A name/partial ID passed. | Pass the APIM service id. |
| Host-name config fails | certificate_id is not an API Management certificate ID, or is inaccessible. |
Reference an azurerm_api_management_certificate in this service. A Key Vault certificate ID or vault.azure.net URL is rejected by the provider. |
| Clients rejected on TLS | Legacy TLS disabled (default). | Only enable tls10/11_enabled if a legacy client truly requires it. |
provider not initialized / features error |
Caller missing features {}. |
Add the features {} block. |
azurerm_api_management_gatewayresource- Sibling modules:
terraform-azurerm-api-management,terraform-azurerm-api-management-api - This module's
SCOPE.md
💙 "Infrastructure as Code should be standardized, consistent, and secure."