Application Gateway and Web Application Firewall
What Is Azure Application Gateway?
Azure Application Gateway is a Layer 7 (HTTP/HTTPS) load balancer and application delivery controller. Unlike Traffic Manager (DNS-level), Application Gateway sits in the data path and can inspect, route, and modify HTTP traffic.
Official Documentation: Application Gateway overview
Application Gateway vs Traffic Manager
Application Gateway
Traffic Manager
Common pattern: Use Traffic Manager for global DNS-based routing across regions, with Application Gateway in each region for Layer 7 load balancing and WAF.
Architecture
Internet
↓
Application Gateway (with WAF)
↓ URL-based routing
↓ SSL termination
↓ Header rewriting
↓
┌─────────────────────────────────────────┐
│ Backend Pool A Backend Pool B │
│ /api/* → APIM /web/* → App Svc │
│ (api-prod.azure.com) (web-prod.azure) │
└─────────────────────────────────────────┘
SKU Tiers
| SKU | Features | Use Case |
|---|---|---|
| Standard v2 | Autoscaling, zone redundancy, static VIP, header rewrite | Production workloads without WAF |
| WAF v2 | All Standard v2 features + Web Application Firewall | Production workloads requiring WAF |
Note: V1 SKUs (Standard and WAF) are deprecated. Always use v2.
Core Components
| Component | Description |
|---|---|
| Frontend IP | Public and/or private IP address receiving traffic |
| Listener | Receives incoming connections on a port and protocol |
| Rule | Maps a listener to a backend pool via routing rules |
| Backend Pool | Group of backend targets (VMs, VMSS, App Services, IPs) |
| HTTP Settings | Backend connection config (port, protocol, timeouts, affinity) |
| Health Probe | Monitors backend health with HTTP/HTTPS requests |
| URL Path Map | Routes requests to different backends based on URL path |
| Rewrite Rules | Modify request/response headers and URL |
Deploying Application Gateway
Bicep Deployment
param location string = resourceGroup().location
param environment string
// Virtual Network and Subnet (required)
resource vnet 'Microsoft.Network/virtualNetworks@2023-09-01' = {
name: 'vnet-appgw-${environment}'
location: location
properties: {
addressSpace: {
addressPrefixes: [ '10.0.0.0/16' ]
}
subnets: [
{
name: 'snet-appgw'
properties: {
addressPrefix: '10.0.1.0/24' // Dedicated subnet for App Gateway
}
}
{
name: 'snet-backend'
properties: {
addressPrefix: '10.0.2.0/24'
}
}
]
}
}
// Public IP
resource publicIp 'Microsoft.Network/publicIPAddresses@2023-09-01' = {
name: 'pip-appgw-${environment}'
location: location
sku: {
name: 'Standard'
}
properties: {
publicIPAllocationMethod: 'Static'
}
}
// Application Gateway with WAF
resource appGateway 'Microsoft.Network/applicationGateways@2023-09-01' = {
name: 'agw-integration-${environment}'
location: location
properties: {
sku: {
name: 'WAF_v2'
tier: 'WAF_v2'
}
autoscaleConfiguration: {
minCapacity: 2
maxCapacity: 10
}
gatewayIPConfigurations: [
{
name: 'appGatewayIpConfig'
properties: {
subnet: {
id: vnet.properties.subnets[0].id
}
}
}
]
frontendIPConfigurations: [
{
name: 'appGwPublicFrontendIp'
properties: {
publicIPAddress: {
id: publicIp.id
}
}
}
]
frontendPorts: [
{
name: 'port_443'
properties: { port: 443 }
}
{
name: 'port_80'
properties: { port: 80 }
}
]
sslCertificates: [
{
name: 'ssl-cert'
properties: {
keyVaultSecretId: 'https://kv-certs-prod.vault.azure.net/secrets/wildcard-cert'
}
}
]
httpListeners: [
{
name: 'listener-https'
properties: {
frontendIPConfiguration: {
id: resourceId('Microsoft.Network/applicationGateways/frontendIPConfigurations', 'agw-integration-${environment}', 'appGwPublicFrontendIp')
}
frontendPort: {
id: resourceId('Microsoft.Network/applicationGateways/frontendPorts', 'agw-integration-${environment}', 'port_443')
}
protocol: 'Https'
sslCertificate: {
id: resourceId('Microsoft.Network/applicationGateways/sslCertificates', 'agw-integration-${environment}', 'ssl-cert')
}
}
}
{
name: 'listener-http-redirect'
properties: {
frontendIPConfiguration: {
id: resourceId('Microsoft.Network/applicationGateways/frontendIPConfigurations', 'agw-integration-${environment}', 'appGwPublicFrontendIp')
}
frontendPort: {
id: resourceId('Microsoft.Network/applicationGateways/frontendPorts', 'agw-integration-${environment}', 'port_80')
}
protocol: 'Http'
}
}
]
backendAddressPools: [
{
name: 'pool-api'
properties: {
backendAddresses: [
{ fqdn: 'apim-prod.azure-api.net' }
]
}
}
{
name: 'pool-webapp'
properties: {
backendAddresses: [
{ fqdn: 'app-web-prod.azurewebsites.net' }
]
}
}
]
backendHttpSettingsCollection: [
{
name: 'settings-api'
properties: {
port: 443
protocol: 'Https'
cookieBasedAffinity: 'Disabled'
requestTimeout: 30
pickHostNameFromBackendAddress: true
probe: {
id: resourceId('Microsoft.Network/applicationGateways/probes', 'agw-integration-${environment}', 'probe-api')
}
}
}
{
name: 'settings-webapp'
properties: {
port: 443
protocol: 'Https'
cookieBasedAffinity: 'Enabled'
requestTimeout: 60
pickHostNameFromBackendAddress: true
probe: {
id: resourceId('Microsoft.Network/applicationGateways/probes', 'agw-integration-${environment}', 'probe-webapp')
}
}
}
]
probes: [
{
name: 'probe-api'
properties: {
protocol: 'Https'
path: '/health'
interval: 30
timeout: 10
unhealthyThreshold: 3
pickHostNameFromBackendHttpSettings: true
match: {
statusCodes: [ '200-399' ]
}
}
}
{
name: 'probe-webapp'
properties: {
protocol: 'Https'
path: '/'
interval: 30
timeout: 10
unhealthyThreshold: 3
pickHostNameFromBackendHttpSettings: true
match: {
statusCodes: [ '200-399' ]
}
}
}
]
urlPathMaps: [
{
name: 'url-path-map'
properties: {
defaultBackendAddressPool: {
id: resourceId('Microsoft.Network/applicationGateways/backendAddressPools', 'agw-integration-${environment}', 'pool-webapp')
}
defaultBackendHttpSettings: {
id: resourceId('Microsoft.Network/applicationGateways/backendHttpSettingsCollection', 'agw-integration-${environment}', 'settings-webapp')
}
pathRules: [
{
name: 'api-path'
properties: {
paths: [ '/api/*' ]
backendAddressPool: {
id: resourceId('Microsoft.Network/applicationGateways/backendAddressPools', 'agw-integration-${environment}', 'pool-api')
}
backendHttpSettings: {
id: resourceId('Microsoft.Network/applicationGateways/backendHttpSettingsCollection', 'agw-integration-${environment}', 'settings-api')
}
}
}
]
}
}
]
requestRoutingRules: [
{
name: 'rule-https'
properties: {
priority: 100
ruleType: 'PathBasedRouting'
httpListener: {
id: resourceId('Microsoft.Network/applicationGateways/httpListeners', 'agw-integration-${environment}', 'listener-https')
}
urlPathMap: {
id: resourceId('Microsoft.Network/applicationGateways/urlPathMaps', 'agw-integration-${environment}', 'url-path-map')
}
}
}
{
name: 'rule-http-redirect'
properties: {
priority: 200
ruleType: 'Basic'
httpListener: {
id: resourceId('Microsoft.Network/applicationGateways/httpListeners', 'agw-integration-${environment}', 'listener-http-redirect')
}
redirectConfiguration: {
id: resourceId('Microsoft.Network/applicationGateways/redirectConfigurations', 'agw-integration-${environment}', 'redirect-http-to-https')
}
}
}
]
redirectConfigurations: [
{
name: 'redirect-http-to-https'
properties: {
redirectType: 'Permanent'
targetListener: {
id: resourceId('Microsoft.Network/applicationGateways/httpListeners', 'agw-integration-${environment}', 'listener-https')
}
includePath: true
includeQueryString: true
}
}
]
webApplicationFirewallConfiguration: {
enabled: true
firewallMode: 'Prevention'
ruleSetType: 'OWASP'
ruleSetVersion: '3.2'
requestBodyCheck: true
maxRequestBodySizeInKb: 128
fileUploadLimitInMb: 100
}
}
}
SSL Termination
Application Gateway handles SSL/TLS decryption, offloading this work from backend servers:
Client → HTTPS → App Gateway → HTTP or HTTPS → Backend
(SSL terminated) (re-encrypted if needed)
SSL Policy
sslPolicy: {
policyType: 'Predefined'
policyName: 'AppGwSslPolicy20220101' // TLS 1.2+ only
}
| Policy | Min TLS Version | Description |
|---|---|---|
AppGwSslPolicy20220101 |
TLS 1.2 | Recommended — strong ciphers only |
AppGwSslPolicy20220101S |
TLS 1.2 | Stricter cipher suite |
AppGwSslPolicy20170401S |
TLS 1.2 | Legacy strong policy |
End-to-End SSL
For end-to-end encryption, configure HTTPS backend settings:
backendHttpSettingsCollection: [
{
name: 'settings-e2e-ssl'
properties: {
port: 443
protocol: 'Https'
trustedRootCertificates: [
{
id: resourceId('Microsoft.Network/applicationGateways/trustedRootCertificates', appGwName, 'backend-root-ca')
}
]
}
}
]
Web Application Firewall (WAF)
The WAF on Application Gateway protects web applications against common exploits and vulnerabilities defined by OWASP.
WAF Modes
| Mode | Behaviour |
|---|---|
| Detection | Logs threats but does not block them — use for initial testing |
| Prevention | Blocks requests that match WAF rules — use for production |
Recommendation: Start in Detection mode, review logs to tune out false positives, then switch to Prevention.
WAF Rule Sets
| Rule Set | Description |
|---|---|
| OWASP 3.2 | Core Rule Set — SQL injection, XSS, command injection, etc. |
| OWASP 3.1 | Previous version — still supported |
| Bot Manager 1.1 | Bot detection and mitigation |
| Custom Rules | User-defined rules with priority-based evaluation |
What WAF on Application Gateway Protects Against
| OWASP Category | Examples |
|---|---|
| SQL Injection | ' OR 1=1 --, union-based attacks |
| Cross-Site Scripting (XSS) | <script>alert('xss')</script> in inputs |
| Command Injection | ;cat /etc/passwd in parameters |
| Local File Inclusion | ../../etc/passwd path traversal |
| Protocol Violations | Malformed HTTP headers, request smuggling |
| Session Fixation | Cookie manipulation attacks |
| Scanner Detection | Known vulnerability scanner signatures |
WAF Policy (Recommended Approach)
WAF policies provide more granular control than the inline webApplicationFirewallConfiguration:
resource wafPolicy 'Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies@2023-09-01' = {
name: 'waf-policy-${environment}'
location: location
properties: {
policySettings: {
state: 'Enabled'
mode: 'Prevention'
requestBodyCheck: true
maxRequestBodySizeInKb: 128
fileUploadLimitInMb: 100
requestBodyInspectLimitInKB: 128
}
managedRules: {
managedRuleSets: [
{
ruleSetType: 'OWASP'
ruleSetVersion: '3.2'
ruleGroupOverrides: [
{
ruleGroupName: 'REQUEST-942-APPLICATION-ATTACK-SQLI'
rules: [
{
ruleId: '942130'
state: 'Disabled' // Disable specific rule causing false positives
action: 'AnomalyScoring'
}
]
}
]
}
{
ruleSetType: 'Microsoft_BotManagerRuleSet'
ruleSetVersion: '1.1'
}
]
exclusions: [
{
matchVariable: 'RequestHeaderNames'
selectorMatchOperator: 'Equals'
selector: 'x-api-key'
}
{
matchVariable: 'RequestBodyPostArgNames'
selectorMatchOperator: 'StartsWith'
selector: 'config_'
}
]
}
customRules: [
{
name: 'BlockSpecificIPs'
priority: 1
ruleType: 'MatchRule'
action: 'Block'
matchConditions: [
{
matchVariables: [
{
variableName: 'RemoteAddr'
}
]
operator: 'IPMatch'
matchValues: [
'203.0.113.0/24'
'198.51.100.0/24'
]
}
]
}
{
name: 'RateLimitByIP'
priority: 2
ruleType: 'RateLimitRule'
rateLimitDuration: 'OneMin'
rateLimitThreshold: 100
action: 'Block'
matchConditions: [
{
matchVariables: [
{
variableName: 'RemoteAddr'
}
]
operator: 'IPMatch'
negationCondition: true
matchValues: [ '10.0.0.0/8' ] // Rate limit external IPs only
}
]
}
{
name: 'AllowHealthProbes'
priority: 3
ruleType: 'MatchRule'
action: 'Allow'
matchConditions: [
{
matchVariables: [
{
variableName: 'RequestUri'
}
]
operator: 'Equal'
matchValues: [ '/health' ]
}
]
}
]
}
}
Associating WAF Policy with Application Gateway
resource appGateway 'Microsoft.Network/applicationGateways@2023-09-01' = {
// ...
properties: {
firewallPolicy: {
id: wafPolicy.id
}
// ... rest of configuration
}
}
Header Rewriting
rewriteRuleSets: [
{
name: 'rewrite-rules'
properties: {
rewriteRules: [
{
name: 'add-security-headers'
ruleSequence: 100
actionSet: {
responseHeaderConfigurations: [
{
headerName: 'Strict-Transport-Security'
headerValue: 'max-age=31536000; includeSubDomains'
}
{
headerName: 'X-Content-Type-Options'
headerValue: 'nosniff'
}
{
headerName: 'X-Frame-Options'
headerValue: 'DENY'
}
]
}
}
{
name: 'forward-client-ip'
ruleSequence: 200
actionSet: {
requestHeaderConfigurations: [
{
headerName: 'X-Forwarded-For'
headerValue: '{var_client_ip}'
}
{
headerName: 'X-Original-Host'
headerValue: '{var_host}'
}
]
}
}
]
}
}
]
Diagnostic Settings and Monitoring
resource appGwDiagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
name: 'appgw-diagnostics'
scope: appGateway
properties: {
workspaceId: logAnalyticsWorkspace.id
logs: [
{ category: 'ApplicationGatewayAccessLog', enabled: true }
{ category: 'ApplicationGatewayPerformanceLog', enabled: true }
{ category: 'ApplicationGatewayFirewallLog', enabled: true }
]
metrics: [
{ category: 'AllMetrics', enabled: true }
]
}
}
KQL: WAF Blocked Requests
AzureDiagnostics
| where ResourceType == "APPLICATIONGATEWAYS"
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Blocked"
| summarize BlockedCount = count() by ruleId_s, ruleGroup_s, details_message_s
| order by BlockedCount desc
| take 20
KQL: Access Log Analysis
AzureDiagnostics
| where ResourceType == "APPLICATIONGATEWAYS"
| where Category == "ApplicationGatewayAccessLog"
| where TimeGenerated > ago(1h)
| summarize
TotalRequests = count(),
AvgLatency = avg(timeTaken_d),
P95Latency = percentile(timeTaken_d, 95),
Errors = countif(httpStatus_d >= 400)
by bin(TimeGenerated, 5m)
| render timechart
KQL: Backend Health
AzureDiagnostics
| where ResourceType == "APPLICATIONGATEWAYS"
| where Category == "ApplicationGatewayAccessLog"
| summarize
Total = count(),
Healthy = countif(httpStatus_d < 500),
Unhealthy = countif(httpStatus_d >= 500)
by serverRouted_s
| extend HealthRate = round(100.0 * Healthy / Total, 2)
Naming Conventions
agw-{workload}-{environment} // Application Gateway
pip-agw-{workload}-{environment} // Public IP for App Gateway
waf-policy-{environment} // WAF Policy
Examples:
agw-integration-prod/pip-agw-integration-prodwaf-policy-prod
Best Practices
- Always use WAF v2 SKU — v1 is deprecated and lacks autoscaling
- Start WAF in Detection mode and tune before switching to Prevention
- Use WAF policies instead of inline configuration for better management
- Configure exclusions for known safe request fields to reduce false positives
- Enable HTTP to HTTPS redirect to enforce encrypted connections
- Use end-to-end SSL for sensitive data — don't stop encryption at the gateway
- Set appropriate request timeouts per backend pool (API vs web app)
- Enable autoscaling with a minimum of 2 instances for high availability
- Deploy in a dedicated subnet — Application Gateway requires its own subnet
- Monitor WAF logs in Log Analytics and set alerts on blocked request spikes
- Use custom rules for IP blocking, geo-filtering, and rate limiting before managed rules
- Combine with Traffic Manager for global multi-region failover