← Back to Guides

Application Gateway Rules in a Zero Trust Architecture

AdvancedAzure Networking2026-03-15

Overview

Adding a new routing rule to an Application Gateway in a Zero Trust architecture is more than just configuring a listener and backend pool. Every component — DNS resolution, network security groups, private endpoints, and certificate validation — must be explicitly verified and locked down.

This guide walks through the complete process of adding an Application Gateway rule in a Zero Trust setup, including Private DNS zones (with legacy Active Directory considerations), NSG rules, and a full verification checklist.

Zero Trust Principle: "Never trust, always verify." Every network flow must be explicitly permitted, authenticated, and encrypted. No implicit trust based on network location.

Zero Trust Pillars Applied to Application Gateway

Pillar Application to App Gateway
Verify explicitly Mutual TLS, certificate validation on every backend connection
Least privilege access NSGs restrict traffic to only required ports and sources
Assume breach WAF in Prevention mode, all traffic logged and monitored, network segmentation enforced

Architecture — Zero Trust Network Topology

Internet
    ↓
[Azure DDoS Protection]
    ↓
[Public IP — Static, Standard SKU]
    ↓
┌──────────────────────────────────────────────────────────┐
│  Application Gateway Subnet (10.0.1.0/24)                │
│  NSG: Allow 443/80 inbound from Internet                 │
│       Allow GatewayManager inbound (65200-65535)         │
│       Deny all other inbound                             │
│                                                          │
│  ┌─────────────────────────────────────┐                 │
│  │  Application Gateway (WAF_v2)       │                 │
│  │  - Frontend: Public + Private IP    │                 │
│  │  - WAF Policy: Prevention mode      │                 │
│  │  - SSL Policy: TLS 1.2+            │                 │
│  │  - End-to-end SSL enabled           │                 │
│  └───────────────┬─────────────────────┘                 │
└──────────────────┼───────────────────────────────────────┘
                   │ (Private link / VNet routing)
                   ↓
┌──────────────────────────────────────────────────────────┐
│  Backend Subnet (10.0.2.0/24)                            │
│  NSG: Allow inbound ONLY from App Gateway subnet         │
│       on required ports (443)                            │
│       Deny all other inbound                             │
│                                                          │
│  ┌────────────┐  ┌────────────┐  ┌────────────────────┐ │
│  │  App Svc   │  │  APIM      │  │  VM / VMSS         │ │
│  │  (PE)      │  │  (PE)      │  │  (Private IP)      │ │
│  └────────────┘  └────────────┘  └────────────────────┘ │
└──────────────────────────────────────────────────────────┘
                   │
                   ↓
┌──────────────────────────────────────────────────────────┐
│  Private DNS Zones                                       │
│  - privatelink.azurewebsites.net                         │
│  - privatelink.azure-api.net                             │
│  - Custom internal zones (e.g. internal.contoso.com)     │
│  Linked to App Gateway VNet                              │
└──────────────────────────────────────────────────────────┘

Prerequisites Before Adding a Rule

Before creating a new routing rule, confirm the following are in place:

Requirement Detail
App Gateway deployed WAF_v2 SKU with autoscaling, in a dedicated /24 subnet
WAF policy attached Prevention mode with OWASP 3.2 and Bot Manager 1.1
SSL certificate In Key Vault, App Gateway has managed identity with GET secret permission
Diagnostic settings Access, Performance, and Firewall logs sent to Log Analytics
DDoS Protection Standard plan associated with the VNet
Private DNS zones Created and linked to the App Gateway VNet

Step 1 — Define the Backend Target

Determine what the new rule will route to:

Property Example Value
Backend FQDN or IP app-orders-prod.azurewebsites.net (private endpoint)
Backend port 443
Backend protocol HTTPS
Health probe path /health or /api/health
Expected host header app-orders-prod.azurewebsites.net

Create the Backend Pool

// Add to backendAddressPools array
{
  name: 'pool-orders'
  properties: {
    backendAddresses: [
      {
        fqdn: 'app-orders-prod.azurewebsites.net'
      }
    ]
  }
}

Zero Trust Note: Use the private endpoint FQDN, not the public FQDN. The Application Gateway must resolve this via Private DNS to the private IP, ensuring traffic never traverses the public internet.

Step 2 — Configure HTTP Settings (Backend Settings)

// Add to backendHttpSettingsCollection array
{
  name: 'settings-orders'
  properties: {
    port: 443
    protocol: 'Https'
    cookieBasedAffinity: 'Disabled'
    requestTimeout: 30
    pickHostNameFromBackendAddress: true   // Critical for App Service backends
    probe: {
      id: resourceId('Microsoft.Network/applicationGateways/probes', appGwName, 'probe-orders')
    }
    trustedRootCertificates: []           // Not needed for well-known CAs
  }
}

When to Use pickHostNameFromBackendAddress vs Custom Host Header

Scenario Setting
App Service / APIM with private endpoint pickHostNameFromBackendAddress: true
Custom domain on backend hostName: 'orders.contoso.com'
VM/VMSS with internal FQDN hostName: 'orders.internal.contoso.com'

Critical: If the host header does not match what the backend expects, you will get 502 Bad Gateway or 404 Not Found. This is the most common misconfiguration.

Step 3 — Create a Health Probe

// Add to probes array
{
  name: 'probe-orders'
  properties: {
    protocol: 'Https'
    path: '/health'
    interval: 30
    timeout: 10
    unhealthyThreshold: 3
    pickHostNameFromBackendHttpSettings: true
    match: {
      statusCodes: [ '200-399' ]
    }
  }
}

Health Probe Best Practices in Zero Trust

  • Always use HTTPS for probes — never HTTP, even internally
  • Set pickHostNameFromBackendHttpSettings: true to match the host header
  • Use a dedicated health endpoint (/health) that checks downstream dependencies
  • Return 200 only when the application is genuinely ready to serve traffic
  • Set a reasonable unhealthyThreshold (3) — too low causes flapping, too high causes slow failover

Step 4 — Add the Listener

For a New Hostname (Multi-site Listener)

// Add to httpListeners array
{
  name: 'listener-orders'
  properties: {
    frontendIPConfiguration: {
      id: resourceId('Microsoft.Network/applicationGateways/frontendIPConfigurations', appGwName, 'appGwPublicFrontendIp')
    }
    frontendPort: {
      id: resourceId('Microsoft.Network/applicationGateways/frontendPorts', appGwName, 'port_443')
    }
    protocol: 'Https'
    sslCertificate: {
      id: resourceId('Microsoft.Network/applicationGateways/sslCertificates', appGwName, 'wildcard-cert')
    }
    hostName: 'orders.contoso.com'        // Multi-site: route by hostname
    requireServerNameIndication: true      // Required for multi-site HTTPS
  }
}

For an Additional Path on an Existing Listener

If adding a new path under an existing listener (e.g. /orders/*), skip creating a new listener and go to Step 5b to add a path rule instead.

Step 5 — Create the Routing Rule

5a — Basic Rule (Dedicated Listener)

// Add to requestRoutingRules array
{
  name: 'rule-orders'
  properties: {
    priority: 300                          // Must be unique across all rules
    ruleType: 'Basic'
    httpListener: {
      id: resourceId('Microsoft.Network/applicationGateways/httpListeners', appGwName, 'listener-orders')
    }
    backendAddressPool: {
      id: resourceId('Microsoft.Network/applicationGateways/backendAddressPools', appGwName, 'pool-orders')
    }
    backendHttpSettings: {
      id: resourceId('Microsoft.Network/applicationGateways/backendHttpSettingsCollection', appGwName, 'settings-orders')
    }
    rewriteRuleSet: {
      id: resourceId('Microsoft.Network/applicationGateways/rewriteRuleSets', appGwName, 'security-headers')
    }
  }
}

5b — Path-Based Rule (Additional Path on Existing Listener)

// Add to the existing urlPathMaps pathRules array
{
  name: 'orders-path'
  properties: {
    paths: [ '/orders/*' ]
    backendAddressPool: {
      id: resourceId('Microsoft.Network/applicationGateways/backendAddressPools', appGwName, 'pool-orders')
    }
    backendHttpSettings: {
      id: resourceId('Microsoft.Network/applicationGateways/backendHttpSettingsCollection', appGwName, 'settings-orders')
    }
    rewriteRuleSet: {
      id: resourceId('Microsoft.Network/applicationGateways/rewriteRuleSets', appGwName, 'security-headers')
    }
  }
}

Rule Priority Guidance

Priority Range Purpose
1–99 Reserved for HTTP-to-HTTPS redirects
100–199 Primary application rules
200–299 Secondary application rules
300–499 Additional service rules
500+ Catch-all / default rules

Important: Every rule must have a unique priority value. If two rules have the same priority, the deployment will fail.

Step 6 — Private DNS Configuration

This is the step most often missed or misconfigured in Zero Trust environments. The Application Gateway must resolve backend FQDNs to private IPs via Private DNS zones.

6a — Azure Private DNS Zones

For each Azure PaaS service accessed via private endpoint, a corresponding Private DNS zone must exist:

Service Private DNS Zone
App Service / Function App privatelink.azurewebsites.net
API Management privatelink.azure-api.net
Azure SQL privatelink.database.windows.net
Storage (blob) privatelink.blob.core.windows.net
Key Vault privatelink.vaultcore.azure.net
Service Bus privatelink.servicebus.windows.net

Create the Private DNS Zone and Link

// Private DNS Zone
resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: 'privatelink.azurewebsites.net'
  location: 'global'
}

// Link to App Gateway VNet
resource dnsVnetLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
  parent: privateDnsZone
  name: 'link-vnet-appgw'
  location: 'global'
  properties: {
    virtualNetwork: {
      id: vnet.id
    }
    registrationEnabled: false    // Do not auto-register VM records
  }
}

Create the Private Endpoint and DNS Record

// Private Endpoint for App Service
resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-09-01' = {
  name: 'pe-app-orders-prod'
  location: location
  properties: {
    subnet: {
      id: backendSubnet.id
    }
    privateLinkServiceConnections: [
      {
        name: 'plsc-app-orders'
        properties: {
          privateLinkServiceId: appService.id
          groupIds: [ 'sites' ]
        }
      }
    ]
  }
}

// Automatic DNS registration in Private DNS Zone
resource privateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-09-01' = {
  parent: privateEndpoint
  name: 'default'
  properties: {
    privateDnsZoneConfigs: [
      {
        name: 'config1'
        properties: {
          privateDnsZoneId: privateDnsZone.id
        }
      }
    ]
  }
}

After deployment, the DNS zone will contain:

app-orders-prod.azurewebsites.net  →  CNAME  →  app-orders-prod.privatelink.azurewebsites.net
app-orders-prod.privatelink.azurewebsites.net  →  A  →  10.0.2.5

6b — Legacy Active Directory DNS Integration

In environments with an existing on-premises or Azure-hosted Active Directory Domain Services (AD DS) infrastructure, DNS resolution follows a different path. Domain-joined machines and services typically use the AD DNS servers as their primary resolver, not Azure DNS (168.63.129.16).

This means Private DNS zones will not work automatically unless you configure DNS forwarding.

┌────────────────────────────────────────────────────────────────┐
│  Legacy AD DS Environment                                      │
│                                                                │
│  Domain-joined servers / VMs                                   │
│       ↓ DNS query                                              │
│  AD DNS Server (DC01: 10.1.0.4, DC02: 10.1.0.5)              │
│       ↓ Conditional forwarder                                  │
│  Azure DNS Private Resolver (10.0.3.4)                         │
│       ↓                                                        │
│  Azure Private DNS Zone (privatelink.azurewebsites.net)        │
│       ↓                                                        │
│  Resolved: 10.0.2.5 (Private Endpoint IP)                     │
└────────────────────────────────────────────────────────────────┘

Option A — Azure DNS Private Resolver (Recommended)

The modern approach that replaces the need for custom DNS VMs:

// DNS Private Resolver
resource dnsResolver 'Microsoft.Network/dnsResolvers@2022-07-01' = {
  name: 'dnspr-${environment}'
  location: location
  properties: {
    virtualNetwork: {
      id: vnet.id
    }
  }
}

// Inbound Endpoint — AD DNS forwards to this
resource inboundEndpoint 'Microsoft.Network/dnsResolvers/inboundEndpoints@2022-07-01' = {
  parent: dnsResolver
  name: 'inbound'
  location: location
  properties: {
    ipConfigurations: [
      {
        subnet: {
          id: dnsResolverInboundSubnet.id    // Dedicated /28 subnet minimum
        }
        privateIpAllocationMethod: 'Dynamic'
      }
    ]
  }
}

Then configure your AD DNS servers with a conditional forwarder:

Setting Value
DNS Domain privatelink.azurewebsites.net
Forwarder IP Inbound endpoint IP (e.g. 10.0.3.4)
DNS Domain privatelink.azure-api.net
Forwarder IP Inbound endpoint IP (e.g. 10.0.3.4)

Repeat for every privatelink.* zone your environment uses.

On the AD DNS Server (PowerShell)

# Add conditional forwarders on each Domain Controller
Add-DnsServerConditionalForwarderZone `
    -Name "privatelink.azurewebsites.net" `
    -MasterServers 10.0.3.4 `
    -ReplicationScope "Forest"

Add-DnsServerConditionalForwarderZone `
    -Name "privatelink.azure-api.net" `
    -MasterServers 10.0.3.4 `
    -ReplicationScope "Forest"

Add-DnsServerConditionalForwarderZone `
    -Name "privatelink.database.windows.net" `
    -MasterServers 10.0.3.4 `
    -ReplicationScope "Forest"

Add-DnsServerConditionalForwarderZone `
    -Name "privatelink.vaultcore.azure.net" `
    -MasterServers 10.0.3.4 `
    -ReplicationScope "Forest"

Add-DnsServerConditionalForwarderZone `
    -Name "privatelink.blob.core.windows.net" `
    -MasterServers 10.0.3.4 `
    -ReplicationScope "Forest"

Add-DnsServerConditionalForwarderZone `
    -Name "privatelink.servicebus.windows.net" `
    -MasterServers 10.0.3.4 `
    -ReplicationScope "Forest"

Option B — Custom DNS Forwarder VMs (Legacy Approach)

If you cannot deploy Azure DNS Private Resolver (older subscriptions or policy restrictions):

  1. Deploy two VMs in a dedicated subnet running DNS (Windows DNS or BIND)
  2. Configure them to forward privatelink.* queries to 168.63.129.16 (Azure DNS)
  3. Configure all other queries to forward to your AD DNS servers
  4. Set the VNet custom DNS to point to these forwarder VMs

Important: This approach requires managing VM availability, patching, and monitoring. Azure DNS Private Resolver is strongly preferred.

VNet Custom DNS Settings

If your VNet uses custom DNS (pointing to AD DNS servers), ensure the resolution chain works:

resource vnet 'Microsoft.Network/virtualNetworks@2023-09-01' = {
  name: 'vnet-appgw-${environment}'
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [ '10.0.0.0/16' ]
    }
    dhcpOptions: {
      dnsServers: [
        '10.1.0.4'     // AD DNS Server (DC01)
        '10.1.0.5'     // AD DNS Server (DC02)
      ]
    }
    // subnets...
  }
}

Zero Trust DNS Principle: DNS resolution must always resolve private endpoint FQDNs to private IPs. If a query accidentally resolves to a public IP, the Application Gateway will attempt to route traffic over the internet, bypassing your Zero Trust network controls.

6c — Verifying DNS Resolution

From a VM in the same VNet as the Application Gateway, verify resolution:

# Should resolve to private IP (e.g. 10.0.2.5), NOT a public IP
nslookup app-orders-prod.azurewebsites.net

# Expected output:
# Name:    app-orders-prod.privatelink.azurewebsites.net
# Address:  10.0.2.5
# Aliases:  app-orders-prod.azurewebsites.net
# Linux equivalent
dig app-orders-prod.azurewebsites.net +short

# Expected: 10.0.2.5

If you see a public IP, the Private DNS zone is not linked to the VNet, the private endpoint DNS group is misconfigured, or the conditional forwarder is missing from the AD DNS server.

Step 7 — Network Security Group (NSG) Configuration

NSGs are the enforcement layer that makes Zero Trust tangible at the network level. Every subnet must have an NSG, and every rule must be explicitly justified.

Application Gateway Subnet NSG

The Application Gateway subnet has mandatory inbound rules required by Azure:

resource nsgAppGw 'Microsoft.Network/networkSecurityGroups@2023-09-01' = {
  name: 'nsg-snet-appgw-${environment}'
  location: location
  properties: {
    securityRules: [
      // === MANDATORY RULES ===
      {
        name: 'Allow-GatewayManager'
        properties: {
          priority: 100
          direction: 'Inbound'
          access: 'Allow'
          protocol: 'Tcp'
          sourceAddressPrefix: 'GatewayManager'
          sourcePortRange: '*'
          destinationAddressPrefix: '*'
          destinationPortRange: '65200-65535'
          description: 'Required for Azure to manage the Application Gateway'
        }
      }
      {
        name: 'Allow-AzureLoadBalancer'
        properties: {
          priority: 110
          direction: 'Inbound'
          access: 'Allow'
          protocol: '*'
          sourceAddressPrefix: 'AzureLoadBalancer'
          sourcePortRange: '*'
          destinationAddressPrefix: '*'
          destinationPortRange: '*'
          description: 'Required for Azure health probes'
        }
      }

      // === APPLICATION RULES ===
      {
        name: 'Allow-HTTPS-Inbound'
        properties: {
          priority: 200
          direction: 'Inbound'
          access: 'Allow'
          protocol: 'Tcp'
          sourceAddressPrefix: 'Internet'
          sourcePortRange: '*'
          destinationAddressPrefix: '10.0.1.0/24'
          destinationPortRange: '443'
          description: 'Allow HTTPS from internet to App Gateway'
        }
      }
      {
        name: 'Allow-HTTP-Inbound'
        properties: {
          priority: 210
          direction: 'Inbound'
          access: 'Allow'
          protocol: 'Tcp'
          sourceAddressPrefix: 'Internet'
          sourcePortRange: '*'
          destinationAddressPrefix: '10.0.1.0/24'
          destinationPortRange: '80'
          description: 'Allow HTTP for redirect to HTTPS'
        }
      }

      // === DENY ALL ===
      {
        name: 'Deny-All-Inbound'
        properties: {
          priority: 4000
          direction: 'Inbound'
          access: 'Deny'
          protocol: '*'
          sourceAddressPrefix: '*'
          sourcePortRange: '*'
          destinationAddressPrefix: '*'
          destinationPortRange: '*'
          description: 'Deny all other inbound traffic'
        }
      }
    ]
  }
}

Warning: If you omit the GatewayManager rule on ports 65200-65535, the Application Gateway will enter a failed state and stop processing traffic. This is the most common NSG mistake.

Backend Subnet NSG

The backend subnet should only allow traffic from the Application Gateway subnet:

resource nsgBackend 'Microsoft.Network/networkSecurityGroups@2023-09-01' = {
  name: 'nsg-snet-backend-${environment}'
  location: location
  properties: {
    securityRules: [
      {
        name: 'Allow-AppGateway-Inbound'
        properties: {
          priority: 100
          direction: 'Inbound'
          access: 'Allow'
          protocol: 'Tcp'
          sourceAddressPrefix: '10.0.1.0/24'    // App Gateway subnet
          sourcePortRange: '*'
          destinationAddressPrefix: '10.0.2.0/24'
          destinationPortRange: '443'
          description: 'Allow HTTPS from App Gateway subnet only'
        }
      }
      {
        name: 'Deny-All-Inbound'
        properties: {
          priority: 4000
          direction: 'Inbound'
          access: 'Deny'
          protocol: '*'
          sourceAddressPrefix: '*'
          sourcePortRange: '*'
          destinationAddressPrefix: '*'
          destinationPortRange: '*'
          description: 'Deny all other inbound traffic'
        }
      }
    ]
  }
}

NSG Rules When Using Private Endpoints

Private endpoints have specific NSG requirements:

Rule Source Destination Port Notes
Allow App Gateway → PE 10.0.1.0/24 10.0.2.0/24 443 App Gateway to backend private endpoints
Allow Health Probes AzureLoadBalancer 10.0.2.0/24 * Azure internal health checking
Deny All Inbound * * * Explicit deny at a lower priority

Note: NSG support on private endpoints must be enabled on the subnet. Set privateEndpointNetworkPolicies: 'Enabled' on the subnet properties.

{
  name: 'snet-backend'
  properties: {
    addressPrefix: '10.0.2.0/24'
    privateEndpointNetworkPolicies: 'Enabled'    // Required to apply NSG to PEs
    networkSecurityGroup: {
      id: nsgBackend.id
    }
  }
}

Step 8 — SSL/TLS Certificate Configuration

Key Vault Integration (Zero Trust Approach)

Never store certificates directly on the Application Gateway. Use Key Vault with managed identity:

// User-assigned managed identity for App Gateway
resource appGwIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
  name: 'id-appgw-${environment}'
  location: location
}

// Key Vault access policy for the managed identity
resource kvAccessPolicy 'Microsoft.KeyVault/vaults/accessPolicies@2023-07-01' = {
  parent: keyVault
  name: 'add'
  properties: {
    accessPolicies: [
      {
        tenantId: subscription().tenantId
        objectId: appGwIdentity.properties.principalId
        permissions: {
          secrets: [ 'get' ]
          certificates: [ 'get' ]
        }
      }
    ]
  }
}

Certificate Rotation

Application Gateway automatically fetches renewed certificates from Key Vault every 4 hours. Ensure:

  1. Certificate is renewed in Key Vault before expiry
  2. The new certificate retains the same secret name
  3. The managed identity still has GET permissions

Step 9 — WAF Rule Tuning for the New Backend

When adding a new backend, its traffic patterns may trigger WAF false positives. Follow this process:

1. Deploy in Detection Mode First

If adding a rule to an existing Production Application Gateway that already runs WAF in Prevention mode, use a per-listener WAF policy override to run the new rule in Detection mode:

// Per-listener WAF policy (Detection mode for new backend)
resource wafPolicyOrders 'Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies@2023-09-01' = {
  name: 'waf-policy-orders-${environment}'
  location: location
  properties: {
    policySettings: {
      state: 'Enabled'
      mode: 'Detection'           // Detection for tuning
      requestBodyCheck: true
      maxRequestBodySizeInKb: 128
    }
    managedRules: {
      managedRuleSets: [
        {
          ruleSetType: 'OWASP'
          ruleSetVersion: '3.2'
        }
      ]
    }
  }
}

2. Monitor for 1–2 Weeks

// KQL: Find false positives for the new backend
AzureDiagnostics
| where ResourceType == "APPLICATIONGATEWAYS"
| where Category == "ApplicationGatewayFirewallLog"
| where hostname_s == "orders.contoso.com"
| where action_s == "Detected"
| summarize Count = count() by ruleId_s, ruleGroup_s, details_message_s
| order by Count desc

3. Add Exclusions for Confirmed False Positives

4. Switch to Prevention Mode

Step 10 — End-to-End Verification Checklist

After deploying the new rule, verify every layer systematically:

DNS Resolution

# From a VM in the App Gateway VNet
nslookup app-orders-prod.azurewebsites.net
# Must return private IP (10.x.x.x), NOT public IP

# If using legacy AD DNS, test from a domain-joined machine
nslookup app-orders-prod.azurewebsites.net dc01.contoso.com
# Verify the conditional forwarder chain works

Application Gateway Backend Health

# Check backend health via Azure CLI
az network application-gateway show-backend-health \
    --resource-group rg-networking-prod \
    --name agw-integration-prod \
    --query 'backendAddressPools[?backendAddressPool.id contains `pool-orders`]' \
    --output table

Expected: Healthy. If Unhealthy, check:

Symptom Likely Cause Fix
Health probe failing NSG blocking App Gateway → Backend Add NSG rule allowing 10.0.1.0/2410.0.2.0/24:443
Health probe failing DNS not resolving to private IP Check Private DNS zone link to VNet
Health probe failing Backend returning 4xx/5xx Check probe path and host header settings
Backend shows Unknown GatewayManager ports blocked Allow inbound 65200-65535 from GatewayManager

SSL Certificate Validation

# Test SSL from App Gateway's perspective
az network application-gateway ssl-cert list \
    --resource-group rg-networking-prod \
    --gateway-name agw-integration-prod \
    --output table

# Verify certificate expiry
az keyvault certificate show \
    --vault-name kv-certs-prod \
    --name wildcard-cert \
    --query 'attributes.expires' \
    --output tsv

NSG Flow Logs

Enable NSG flow logs to verify traffic is flowing correctly:

// KQL: Check if traffic from App Gateway is reaching the backend
AzureNetworkAnalytics_CL
| where SubType_s == "FlowLog"
| where SrcIP_s startswith "10.0.1."           // App Gateway subnet
| where DestIP_s startswith "10.0.2."          // Backend subnet
| where DestPort_d == 443
| where FlowStatus_s == "A"                   // Allowed
| summarize Count = count() by bin(TimeGenerated, 5m)
| render timechart

WAF Logs

// KQL: Check for blocked requests to the new backend
AzureDiagnostics
| where ResourceType == "APPLICATIONGATEWAYS"
| where Category == "ApplicationGatewayFirewallLog"
| where hostname_s == "orders.contoso.com"
| where action_s == "Blocked"
| project TimeGenerated, ruleId_s, ruleGroup_s, details_message_s, requestUri_s
| order by TimeGenerated desc
| take 50

End-to-End Connectivity Test

# From outside the network — verify the full path
curl -v https://orders.contoso.com/health

# Expected: HTTP 200
# Verify headers include your rewrite rules (HSTS, X-Content-Type-Options, etc.)

Troubleshooting Guide

502 Bad Gateway

Check Command / Action
Backend health Portal → App Gateway → Backend Health
DNS resolution nslookup <backend-fqdn> from App GW VNet — must return private IP
NSG rules Verify App GW subnet can reach backend subnet on port 443
Host header Ensure pickHostNameFromBackendAddress or custom hostName is correct
Backend certificate If using end-to-end SSL, ensure trusted root cert is configured
Probe path Verify the health probe path returns 200 on the backend

403 Forbidden

Check Action
WAF blocking Check Firewall logs for blocked rule IDs
Backend access restriction If App Service, check IP restrictions allow App GW subnet
Custom WAF rules Check custom rule priorities — a broad block rule may match first

Backend Shows Unhealthy

Check Action
Probe timeout Increase from 10s to 30s if backend is slow to start
Probe interval Reduce interval for faster detection
Status code match Ensure match.statusCodes includes codes returned by your health endpoint
Private endpoint approved Check private endpoint connection status is Approved

DNS Resolution Returns Public IP

Check Action
Private DNS zone exists Verify privatelink.azurewebsites.net zone exists
VNet link active Verify zone is linked to the App Gateway VNet
Private endpoint DNS group Verify privateDnsZoneGroups is configured on the PE
AD conditional forwarder Verify forwarder for privatelink.* points to Azure DNS Private Resolver
VNet DNS settings If custom DNS servers, verify forwarder chain resolves correctly

Security Hardening Checklist

Control Status Notes
WAF v2 SKU with Prevention mode Required Never use Standard SKU in Zero Trust
OWASP 3.2 rule set enabled Required Plus Bot Manager 1.1
End-to-end SSL (TLS 1.2+) Required No unencrypted backend connections
SSL Policy AppGwSslPolicy20220101 Required Enforces TLS 1.2 with strong ciphers
Certificates in Key Vault Required Never store PFX on the gateway directly
Managed identity for Key Vault access Required No shared secrets or keys
NSG on every subnet Required Explicit deny-all with specific allows
GatewayManager ports open (65200-65535) Required App Gateway will fail without this
DDoS Protection Standard Recommended On the VNet
Private endpoints for all backends Required No public internet routing for backends
Private DNS zones linked Required Backends must resolve to private IPs
Diagnostic logs to Log Analytics Required Access, Performance, and Firewall logs
NSG Flow Logs enabled Recommended For traffic auditing and troubleshooting
HTTP-to-HTTPS redirect Required Never serve over plain HTTP
Security headers via rewrite rules Recommended HSTS, X-Content-Type-Options, X-Frame-Options
Custom WAF rules for rate limiting Recommended Protect against brute force and DDoS

Official Microsoft Resources