Terraform infrastructure for deploying Azure API Management (APIM) in Internal VNet mode with Application Gateway WAF v2 as the public-facing entry point.
┌─────────────────────────────────────────────────────────────────┐
│ Azure VNet │
│ │
┌──────────┐ │ ┌─────────────────────┐ ┌─────────────────────────────┐ │
│ │ HTTPS (443) │ │ Application │ │ API Management │ │
│ Client │───────────────────►│ │ Gateway WAF v2 │─────►│ (Internal VNet Mode) │ │
│ │ │ │ │ │ │ │
└──────────┘ │ │ • WAF Policy │ │ • Gateway/Proxy │ │
│ │ • SSL Termination │ │ • Developer Portal │ │
│ │ • URL Routing │ │ • Management API │ │
│ │ • Health Probes │ │ • SCM/Git │ │
│ │ │ │ │ │
│ └─────────────────────┘ └──────────────┬──────────────┘ │
│ ▲ │ │
│ │ Private Endpoint │ │
│ └──────────────────────────────────┘ │
│ │ │
└──────────────────────────────────────────────┼──────────────────┘
▼
┌─────────────────────┐
│ Backend Services │
└─────────────────────┘
Client requests flow through the Application Gateway (WAF v2), are forwarded to API Management over its private endpoint, and APIM routes them to the configured backend services.
- Two-Phase Deployment: Deploy Key Vault first to upload certificates, then deploy full infrastructure
- Private APIM Deployment: API Management deployed in Internal VNet mode, accessible only via private endpoint
- WAF Protection: Application Gateway with WAF v2 using OWASP 3.2 and Microsoft Bot Manager rule sets
- Multi-Domain Support: Separate custom domains for gateway, developer portal, management, and SCM endpoints
- SSL/TLS Termination: Centralized certificate management via Azure Key Vault (provisioned by this module)
- Autoscaling: Automatic scaling for stage and production environments based on CPU utilization
- Infrastructure as Code: Fully automated deployment using Terraform and Azure Verified Modules
This project uses the following Azure Verified Modules (AVM):
| Module | Version | Purpose |
|---|---|---|
| avm-res-keyvault-vault | 0.10.0 | Key Vault for certificate storage |
| avm-res-apimanagement-service | 0.0.5 | API Management service |
| avm-res-network-applicationgateway | 0.4.3 | Application Gateway WAF v2 |
| avm-res-network-virtualnetwork//subnet | 0.16.0 | Subnet provisioning |
| avm-utl-network-ip-addresses | 0.1.0 | IP address calculation |
- Terraform >= 1.12.0
- Azure CLI >= 2.50.0
- Azure subscriptions with appropriate permissions:
- APIM Subscription: Contributor + User Access Administrator (for RBAC assignments)
- Connectivity Subscription: Reader access to private DNS zones
- Existing resources:
- Virtual Network with available address space
- Log Analytics workspace
- Private DNS zones for
privatelink.azure-api.net
- SSL certificate (PFX format) for custom domains (wildcard or SAN certificate)
azure-api-management/
├── main.tf # Main infrastructure resources
├── variables.tf # Input variable definitions
├── locals.tf # Local values and computed variables
├── outputs.tf # Output definitions
├── terraform.tf # Provider configuration
├── terraform.tfvars.sample # Sample variable values
└── README.md # This file
| Resource | Phase | Description |
|---|---|---|
| Key Vault | 1 | Stores SSL certificates for custom domains |
| Subnets | 1 | Dedicated subnets for APIM, AppGW, and private endpoints |
| Network Security Groups | 1 | Security rules for AppGW and APIM subnets |
| API Management | 2 | Internal VNet mode instance with private endpoint |
| Application Gateway | 2 | WAF v2 with public IP for external access |
| WAF Policy | 2 | OWASP 3.2 + Bot Manager protection |
| User Assigned Identities | 2 | Separate identities for APIM and AppGW |
| Role Assignments | 2 | Key Vault access for certificate retrieval |
| Autoscale Settings | 2 | CPU-based scaling (stage/prod only) |
| Variable | Description | Example |
|---|---|---|
deploy_keyvault_only |
Phase 1 deployment flag | true / false |
connectivity_subscription_id |
Subscription with DNS zones | 00000000-... |
apim_subscription_id |
Subscription for APIM deployment | 00000000-... |
environment |
Environment name | dev, stage, prod |
location |
Azure region | eastus2 |
custom_domain |
Gateway/proxy domain | api.example.com |
custom_domain_developer_portal |
Developer portal domain | developer.example.com |
custom_domain_management |
Management API domain | management.example.com |
custom_domain_scm |
SCM/Git domain | scm.example.com |
publisher_email |
APIM publisher email | admin@example.com |
publisher_name |
APIM publisher name | Example Org |
virtual_network_address_space |
VNet CIDR | 10.100.0.0/22 |
| Setting | Dev | Stage | Prod |
|---|---|---|---|
| APIM SKU | Developer | Premium | Premium |
| APIM Capacity | 1 | 1-3 | 1-3 |
| AppGW Capacity | 1-4 | 2-6 | 2-10 |
| Subnet Size | /27 | /26 | /26 |
| Autoscaling | No | Yes | Yes |
| KV Purge Protection | No | No | Yes |
This module uses a two-phase deployment approach to handle the certificate dependency.
First, deploy only the Key Vault and network infrastructure so you can upload the SSL certificate.
# Clone the repository
git clone https://github.com/your-org/azure-api-management.git
cd azure-api-management
# Copy and configure variables
cp terraform.tfvars.sample terraform.tfvars
# Edit terraform.tfvars - set deploy_keyvault_only = true# Login to Azure
az login
az account set --subscription "<apim_subscription_id>"
# Initialize and deploy Phase 1
terraform init
terraform plan -out=tfplan
terraform apply tfplanAfter Phase 1 completes, upload your SSL certificate to the Key Vault:
# Get the Key Vault name from outputs
terraform output keyvault_name
# Upload the certificate (PFX format)
az keyvault certificate import \
--vault-name "<keyvault-name>" \
--name "<certificate-name-from-tfvars>" \
--file "/path/to/certificate.pfx" \
--password "<pfx-password>"Once the certificate is uploaded, deploy the remaining infrastructure:
# Edit terraform.tfvars - set deploy_keyvault_only = false# Plan and apply Phase 2
terraform plan -out=tfplan
terraform apply tfplanAfter deployment, create DNS records pointing your custom domains to the Application Gateway public IP:
# Get the Application Gateway public IP
terraform output appgw_public_ip
# Create A records for each custom domain:
# - api.example.com -> <AppGW Public IP>
# - developer.example.com -> <AppGW Public IP>
# - management.example.com -> <AppGW Public IP>
# - scm.example.com -> <AppGW Public IP>All public custom domains terminate at the Application Gateway (WAF v2) and are forwarded to API Management over its private endpoint:
| Domain | Listener | Backend |
|---|---|---|
api.example.com |
gateway-https-listener | APIM gateway/proxy |
developer.example.com |
developer-portal-https-listener | APIM developer portal |
management.example.com |
management-https-listener | APIM management API |
scm.example.com |
scm-https-listener | APIM SCM/Git |
HTTP listeners on port 80 redirect to their HTTPS counterparts. API Management is responsible for routing requests to the configured backend services.
| Direction | Priority | Name | Port | Source | Purpose |
|---|---|---|---|---|---|
| Inbound | 100 | AllowHTTPS | 443 | Internet | Client traffic |
| Inbound | 110 | AllowHTTP | 80 | Internet | HTTP redirect |
| Inbound | 200 | AllowGatewayManager | 65200-65535 | GatewayManager | Health probes |
| Inbound | 210 | AllowAzureLoadBalancer | Any | AzureLoadBalancer | LB health |
| Inbound | 4096 | DenyAllInbound | Any | Any | Default deny |
| Outbound | 100 | AllowAllOutbound | Any | Any | Backend communication |
| Direction | Priority | Name | Port | Source | Purpose |
|---|---|---|---|---|---|
| Inbound | 100 | AllowAppGateway | 443, 3443 | AppGW Subnet | Gateway traffic |
| Inbound | 110 | AllowManagement | 3443 | ApiManagement | Management plane |
| Inbound | 120 | AllowLoadBalancer | 6390 | AzureLoadBalancer | Health monitoring |
| Inbound | 4096 | DenyAllInbound | Any | Any | Default deny |
| Outbound | 100-180 | Various | Various | Various | Azure services |
| Outbound | 4096 | DenyAllOutbound | Any | Any | Default deny |
| Output | Description |
|---|---|
apim_id |
API Management resource ID |
apim_name |
API Management instance name |
apim_gateway_url |
APIM gateway URL |
apim_private_ip_addresses |
Private IP addresses for APIM |
appgw_id |
Application Gateway resource ID |
appgw_name |
Application Gateway name |
appgw_public_ip |
Application Gateway public IP address |
subnet_ids |
Map of created subnet IDs |
appgw_nsg_id |
Application Gateway NSG ID |
apim_nsg_id |
API Management NSG ID |
Symptom: APIM or AppGW fails to retrieve certificate from Key Vault
Solution: Verify the managed identities have the correct RBAC roles:
Key Vault Secrets User- For reading certificate secretsKey Vault Certificate User- For reading certificate metadata
# Check role assignments
az role assignment list --scope "/subscriptions/.../resourceGroups/.../providers/Microsoft.KeyVault/vaults/<keyvault-name>"Symptom: Application Gateway backend health shows unhealthy
Solution:
- Verify NSG rules allow traffic from AppGW subnet to APIM subnet
- Check APIM is responding on the probe path (
/status-0123456789abcdef) - Verify the host header in probe configuration matches the custom domain
Symptom: Clients receive 502 errors when accessing the API
Solution:
- Check APIM backend pool is using the correct FQDN
- Verify private DNS resolution for
*.privatelink.azure-api.net - Confirm the backend HTTP setting has the correct
host_nameconfigured
Symptom: Developer portal shows blank page or errors
Solution:
- Ensure CORS is configured in APIM for the developer portal domain
- Verify the developer portal custom domain is correctly configured
- Check the portal has been published after APIM deployment
# View Terraform state
terraform state list
# Check specific resource
terraform state show 'module.apim.azurerm_api_management.this'
# Refresh state from Azure
terraform refresh
# View outputs
terraform output
# Destroy specific resource (use with caution)
terraform destroy -target='module.apim'- Network Isolation: APIM is deployed in Internal VNet mode with no public endpoint
- WAF Protection: All traffic passes through WAF with OWASP 3.2 rules enabled
- TLS Enforcement: HTTP traffic is automatically redirected to HTTPS
- Key Vault Integration: Certificates are stored securely in Key Vault with RBAC access
- NSG Rules: Restrictive security groups limit traffic to required ports only
- Managed Identities: User-assigned identities avoid credential management
- Create a feature branch
- Make your changes
- Run
terraform fmtandterraform validate - Submit a pull request
See LICENSE for details.