Blog Post

Azure Networking Blog
7 MIN READ

Integrating Azure Application Gateway v2 with Azure API Management for secure and scalable API

ranjsharma's avatar
ranjsharma
Icon for Microsoft rankMicrosoft
Nov 18, 2025

This post walks through integrating Azure Application Gateway v2 (with WAF) and Azure API Management to deliver a secure, scalable, and enterprise-grade API front door. We’ll cover reference architectures, networking, certificates, Azure API Management policies, zero-trust patterns with private endpoints, and end-to-end automation using Terraform and Azure DevOps.

Why Application Gateway v2 + Azure API Management?

- Layer-7 routing with path-based rules, host headers, URL rewrites, and WAF protection (OWASP Core Rule Set).  

- Azure API Management provides API abstraction, versioning, throttling, caching, JWT validation, and per-API policies.  

- Combined, App Gateway becomes the internet-facing secure entry point and Azure API Management the control plane for API governance.

 

Scenario:

Internet → App Gateway (WAF) → Azure API Management (External) → Backends

Best when Azure API Management needs to be publicly reachable but protected by WAF and central routing.

[Client] ─HTTPS──> [App Gateway v2 (WAF)] ─HTTPS──> [Azure API Management (External)] ─> [Private/On-prem/Azure Backends]

Pros: Simple, fast to implement, WAF in front, supports CDN/Front Door chaining.  

Cons: Azure API Management is public; additional steps required for IP allow-lists and mTLS.

 

Scenario2:

Internet → App Gateway (WAF) → Azure API Management (Internal) via Private Endpoint

Azure API Management is internal; only App Gateway is public. Zero-trust friendly.

[Client] ─HTTPS──> [App Gateway v2 (WAF, Public)] ─HTTPS──> [Private Endpoint] ─> [Azure API Management (Internal)] ─> [Backends]

Pros: Azure API Management is not exposed to the internet; traffic flows through App Gateway + Private Link.  

Cons: Requires vNet planning, DNS, and App Gateway-to-Private Link name resolution.

 

Scenario3: 

Azure API Management (External) → App Gateway (Internal) → Private Backends

Azure API Management is the public front door; App Gateway does L7 routing to internal services.

[Client] ─HTTPS──> [Azure API Management (External)] ─HTTPS──> [App Gateway (Internal/WAF)] ─> [Backends]

Pros: Privileged Identity Management security & governance is your internet front door.  

Cons: More Azure API Management policy work; App Gateway must be reachable from Azure API Management.

 

Network & DNS design checklist:

- Virtual networks & subnets:

  - App Gateway-Subnet (required dedicated subnet)  

  - Azure API Management-Subnet (for internal tier)  

  - Shared-services-Subnet for Bastion/jumpbox/logging  

- Private Link: Enable Azure API Management private endpoint in Azure API Management-Subnet. 

- Private DNS Zones: privatelink.azure-api.net for Azure API Management, custom zones for backends.  

- Name resolution: App Gateway must resolve Azure API Management private FQDN via vNet DNS or Azure Private DNS.  

- Firewall & NSGs: Restrict inbound/outbound; allow only required ports to Azure API Management, Key Vault, Log Analytics.  

- Hybrid Connectivity: Site-to-site VPN or ExpressRoute for on-prem backends; consider Azure Firewall or NVA.

 

Certificates & TLS

- Custom Domains:

  - App Gateway: api.contoso.com

  - Azure API Management: gateway.contoso.com (with custom hostname on Azure API Management)  

- TLS Ports: HTTPS 443 end-to-end; disable TLS 1.0/1.1.  

- Cert storage: Use Azure Key Vault for SSL certs; integrate App Gateway & Azure API Management with Key Vault (managed identity).  

- mTLS (Client Certs): Enforce on Azure API Management with policies; optionally on App Gateway via mutual auth for selected listeners.

WAF (Web Application Firewall) on App Gateway v2

- Modes: Detection vs prevention (recommend prevention once tuned).  

- CRS: Start with 3.2; baseline exclusions for APIs (JSON payloads).  

- Managed rules: Enable bot protection, set anomaly scoring, create exclusions for headers like Authorization.  

- Logging: Send WAF logs to Log Analytics; build alerts for blocked requests spikes.

Azure API Management Policies – Common patterns

- Inbound: `validate-jwt`, `check-header`, `rate-limit`, `ip-filter`, `set-backend-service`  

- Backend: `retry`, `forward-request` with mTLS  

- Outbound: `set-header`, `find-and-replace`, `cache`  

- Global vs API vs Operation: Keep global minimal; override at API/operation for precision.  

- Dev, Test, Prod: Parameterize via named values and Key Vault references.

 

Example – JWT validation and rate limit:

xml

<policies>

  <inbound>

    <base />

    <validate-jwt header-name="Authorization" failed-validation-httpcode="401">

      <openid-config url="https://login.microsoftonline.com/<tenant-id>/v2.0/.well-known/openid-configuration" />

      <audiences>

        <audience>api://contoso-app-id</audience>

      </audiences>

      <issuers>

        <issuer>https://sts.windows.net/<tenant-id>/</issuer>

      </issuers>

    </validate-jwt>

    <rate-limit calls="100" renewal-period="60" />

  </inbound>

  <backend>

    <forward-request />

  </backend>

  <outbound>

    <base />

  </outbound>

</policies>

## Terraform – Core Resources (App Gateway v2 + Azure API Management)

Note: Simplified example; parameterize for prod, add Key Vault integrations, diagnostics, and role assignments.

# Variables (example)

variable "location" { default = "eastus" }

variable "rg_name" { default = "rg-appgw-apim" }

variable "vnet_name" { default = "vnet-core" }

variable "appgw_subnet" { default = "snet-appgw" }

variable "apim_subnet" { default = "snet-apim" }

 

provider "azurerm" { features {} }

 

resource "azurerm_resource_group" "rg" {

  name     = var.rg_name

  location = var.location

}

 

resource "azurerm_virtual_network" "vnet" {

  name                = var.vnet_name

  location            = var.location

  resource_group_name = azurerm_resource_group.rg.name

  address_space       = ["10.10.0.0/16"]

}

 

resource "azurerm_subnet" "snet_appgw" {

  name                 = var.appgw_subnet

  resource_group_name  = azurerm_resource_group.rg.name

  virtual_network_name = azurerm_virtual_network.vnet.name

  address_prefixes     = ["10.10.1.0/24"]

}

 

resource "azurerm_subnet" "snet_apim" {

  name                 = var.apim_subnet

  resource_group_name  = azurerm_resource_group.rg.name

  virtual_network_name = azurerm_virtual_network.vnet.name

  address_prefixes     = ["10.10.2.0/24"]

}

 

# Public IP for App Gateway

resource "azurerm_public_ip" "appgw_pip" {

  name                = "pip-appgw"

  resource_group_name = azurerm_resource_group.rg.name

  location            = var.location

  allocation_method   = "Static"

  sku                 = "Standard"

}

 

# Application Gateway v2 (WAF)

resource "azurerm_application_gateway" "appgw" {

  name                = "agw-v2-waf"

  resource_group_name = azurerm_resource_group.rg.name

  location            = var.location

  sku {

    name = "WAF_v2"

    tier = "WAF_v2"

    capacity = 2

  }

  gateway_ip_configuration {

    name      = "appgw-ipcfg"

    subnet_id = azurerm_subnet.snet_appgw.id

  }

  frontend_port {

    name = "https-port"

    port = 443

  }

  frontend_ip_configuration {

    name                 = "appgw-feip"

    public_ip_address_id = azurerm_public_ip.appgw_pip.id

  }

  ssl_certificate {

    name     = "ssl-agw"

    data     = filebase64("certs/agw.pfx")

    password = var.agw_pfx_password

  }

  http_listener {

    name                           = "listener-https"

    frontend_ip_configuration_name = "appgw-feip"

    frontend_port_name             = "https-port"

    protocol                       = "Https"

    ssl_certificate_name           = "ssl-agw"

    host_name                      = "api.contoso.com"

  }

  backend_address_pool {

    name = "apim-bepool"

    # For Azure API Management private endpoint, use FQDN via custom probe, or IP when static

    fqdns = ["gateway.contoso.internal"]

  }

  backend_http_settings {

    name                  = "https-settings"

    cookie_based_affinity = "Disabled"

    port                  = 443

    protocol              = "Https"

    pick_host_name_from_backend_address = true

    request_timeout       = 30

    probe_name            = "apim-probe"

  }

  probe {

    name                = "apim-probe"

    protocol            = "Https"

    path                = "/status-0123456789abcdef"

    pick_host_name_from_backend_http_settings = true

    interval            = 30

    timeout             = 30

    unhealthy_threshold = 3

  }

  request_routing_rule {

    name                       = "route-to-apim"

    rule_type                  = "Basic"

    http_listener_name         = "listener-https"

    backend_address_pool_name  = "apim-bepool"

    backend_http_settings_name = "https-settings"

  }

  waf_configuration {

    enabled            = true

    firewall_mode      = "Prevention"

    rule_set_type      = "OWASP"

    rule_set_version   = "3.2"

  }

}

 

# Azure API Management (Developer SKU for demo – use Premium for prod & VNET integration)

resource "azurerm_api_management" "apim" {

  name                = "apim-contoso"

  location            = var.location

  resource_group_name = azurerm_resource_group.rg.name

  publisher_name      = "Contoso"

  publisher_email     = "admin@contoso.com"

  sku_name            = "Developer_1"

  virtual_network_type = "None" # Use "Internal" for vNet, then add private endpoint

}

## Azure DevOps – CI/CD YAML (App Gateway + Azure API Management via Terraform)

`yaml

trigger:

  branches:

    include: [ main ]

 

pool:

  vmImage: 'ubuntu-latest'

 

variables:

  TF_VERSION: '1.8.5'

  ARM_USE_MSI: true

 

stages:

- stage: Validate

  jobs:

  - job: tf_validate

    steps:

    - task: Bash@3

      displayName: 'Install Terraform'

      inputs:

        targetType: 'inline'

        script: |

          sudo apt-get update && sudo apt-get install -y unzip

          curl -L -o tf.zip https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip

          unzip tf.zip && sudo mv terraform /usr/local/bin/

          terraform -version

    - task: AzureCLI@2

      displayName: 'Terraform init & validate'

      inputs:

        azureSubscription: '$(AZURE_SERVICE_CONNECTION)'

        scriptType: 'bash'

        scriptLocation: 'inlineScript'

        inlineScript: |

          terraform init -backend-config=backend.hcl

          terraform fmt -check

          terraform validate

 

- stage: Plan

  jobs:

  - job: tf_plan

    steps:

    - task: AzureCLI@2

      inputs:

        azureSubscription: '$(AZURE_SERVICE_CONNECTION)'

        scriptType: 'bash'

        scriptLocation: 'inlineScript'

        inlineScript: |

          terraform plan -out=tfplan

    - publish: tfplan

      artifact: tfplan

 

- stage: Apply

  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))

  jobs:

  - job: tf_apply

    steps:

    - download: current

      artifact: tfplan

    - task: AzureCLI@2

      inputs:

        azureSubscription: '$(AZURE_SERVICE_CONNECTION)'

        scriptType: 'bash'

        scriptLocation: 'inlineScript'

        inlineScript: |

          terraform apply -auto-approve tfplan

 

##Observability & diagnostics

- Access Logs: App Gateway & WAF logs to Log Analytics; query with KQL.  

- Azure API Management Metrics: Requests, backend duration, cache hits; enable diagnostic settings to Log Analytics/Storage/Event Hub.  

- End-to-end tracing: Correlate `x-correlation-id` across App Gateway, Azure API Management, and backend logs.  

- Alerts: 4xx/5xx thresholds, WAF blocks spike, Azure API Management throttling events, TLS certificate expiry.

 

## Security hardening

- Enforce TLS 1.2+, disable weak ciphers.  

- WAF exclusions tuned minimally; regular rule reviews.  

- Azure API Management IP allow-lists for admin endpoints; use RBAC and separate admin vs gateway hostnames.  

- Private Endpoints for Azure API Management & backends; deny public network access where possible.  

- mTLS from App Gateway→Azure API Management or Client→Azure API Management when required (Key Vault for client certs).  

- DDoS Protection on vNet with public exposure; consider Azure Front Door WAF for global edge.

 

## Cost & Performance

- Right-size App Gateway v2 capacity; enable autoscaling for variable traffic.  

- Use Azure API Management Premium only if you need vNet, multi-region, or zone redundancy; otherwise consider Standard/Developer for non-prod.  

- Caching policies in Azure API Management reduce backend load; use response compression.  

- Health probes optimized for backend responsiveness (avoid tight intervals).

 

## Troubleshooting 

- App Gateway 502/504: Check backend health, probe path, SNI/host header, TLS ciphers, DNS resolution to Azure API Management.  

- Azure API Management 401/403:  Validate JWT audience/issuer; clock skew; named values; policy order.  

- Private Endpoint:  DNS record in `privatelink.azure-api.net` exists; App Gateway subnet can resolve; NSG not blocking.  

- Cert Issues: PFX password correct; full chain present; key usage supports server auth.  

- Performance: Turn on App Gateway autoscaling; review Azure API Management throttling; check backend rate limits.

 

## Production checklist

- Custom domains & cert rotation via Key Vault  

- WAF in Prevention with tuned exclusions  

- Azure API Management policies for auth, rate limiting, cache, headers  

- Private endpoints + DNS validated end-to-end  

- Autoscaling & health probes tuned  

- Diagnostics & alerts configured  

- CI/CD gated approvals; Terraform state secured  

- Runbooks for failover & certificate renewal  

 

 

Updated Nov 18, 2025
Version 2.0
No CommentsBe the first to comment