Lab 08 - Set Up Rate Limiting with XFF Grouping¶
Overview¶
In this lab, you will configure rate limiting custom rules on both Azure Application Gateway WAF and Azure Front Door WAF. Rate limiting protects your applications from abuse by restricting the number of requests a client can make within a specified time window. You will also configure X-Forwarded-For (XFF) header-based grouping to accurately identify clients behind proxies or CDNs, and test rate limiting under burst traffic conditions.
Objectives¶
- Understand rate limiting concepts: threshold, time window, and group-by keys
- Create a rate limit rule on Application Gateway WAF using client IP grouping
- Create a rate limit rule on Front Door WAF using X-Forwarded-For grouping
- Test rate limiting by sending burst traffic that exceeds thresholds
- Verify rate limiting triggers and analyze the resulting logs
- Configure advanced geo-based rate limiting rules
- Learn rate limiting best practices for production
Prerequisites¶
- Completed Labs 05-07 (Prevention mode, Front Door WAF, Bot Protection)
- Application Gateway WAF v2 with WAF policy in Prevention mode
- Front Door Premium with WAF policy in Prevention mode
- Backend web apps deployed and accessible
- Log Analytics workspace with WAF diagnostics enabled
- Access to Azure Portal and Azure CLI
Section 1: Understand Rate Limiting Concepts¶
1.1 How Rate Limiting Works¶
Rate limiting tracks the number of requests from a specific source within a defined time window. When the threshold is exceeded, subsequent requests are blocked until the window resets.
Time Window: 1 minute Threshold: 100 requests
─────────────────────────────────────────────────────
Request 1 → ✅ Allow
Request 2 → ✅ Allow
...
Request 99 → ✅ Allow
Request 100 → ✅ Allow (threshold reached)
Request 101 → ❌ Block (429/403)
Request 102 → ❌ Block
...
Window resets after 1 minute
Request 1 → ✅ Allow (new window)
1.2 Key Configuration Parameters¶
| Parameter | Description | Typical Values |
|---|---|---|
| Threshold | Maximum number of requests allowed in the time window | 100-1000 per window |
| Time window | Duration over which requests are counted | 1 minute or 5 minutes |
| Group-by key | How to identify individual clients for counting | Client IP, XFF header, Geo location |
| Action | What to do when the threshold is exceeded | Block (403), Log, Redirect |
| Match conditions | Optional: apply rate limiting only to specific paths or methods | URI path, HTTP method |
1.3 Group-By Options¶
| Group-By Key | Use Case | Considerations |
|---|---|---|
| SocketAddr (Client IP) | Direct client connections | Accurate when clients connect directly; inaccurate behind NAT/proxy |
| X-Forwarded-For | Clients behind CDN/proxy/load balancer | Identifies the real client IP from the XFF header |
| GeoLocation | Country-level rate limiting | Useful for geo-based traffic policies |
| None | Global rate limit | Counts all requests together regardless of source |
1.4 Application Gateway vs Front Door Rate Limiting¶
| Feature | Application Gateway | Front Door |
|---|---|---|
| Time windows | 1 min, 5 min | 1 min, 5 min |
| Group-by options | ClientAddr, SocketAddr, GeoLocation | SocketAddr, GeoLocation |
| XFF support | Via ClientAddr (parses XFF) | Via custom match conditions |
| Action on exceed | Block (403) | Block (403), Log, Redirect |
| Scope | Per Application Gateway instance | Per Front Door PoP |
Section 2: Create Rate Limit Rule (Application Gateway)¶
2.1 Create via Azure Portal¶
-
Navigate to the Azure Portal → Resource Groups → your workshop resource group.
-
Open the Application Gateway WAF Policy (e.g.,
wafpolicy). -
Click Custom rules in the left-hand menu.
-
Click + Add custom rule.
-
Configure the rate limit rule:
| Setting | Value |
|---|---|
| Custom rule name | RateLimitByClientIP |
| Priority | 100 |
| Rule type | Rate limit |
| Rate limit duration | 1 minute |
| Rate limit threshold | 100 |
| Group rate limit traffic by | Client address |
- Add a match condition:
| Setting | Value |
|---|---|
| Match type | String |
| Match variable | RequestUri |
| Operator | Any |
| Negate | No |
💡 Note: Using
RequestUriwithAnyoperator matches all incoming requests, applying the rate limit globally.
-
Set the Action to Block (returns 403 Forbidden).
-
Click Add to save the rule.
-
Click Save on the custom rules page.
2.2 Create via Azure CLI¶
# Create rate limit custom rule on Application Gateway WAF policy
az network application-gateway waf-policy custom-rule create \
--policy-name <appgw-waf-policy-name> \
--resource-group <resource-group-name> \
--name RateLimitByClientIP \
--priority 100 \
--rule-type RateLimitRule \
--rate-limit-duration OneMin \
--rate-limit-threshold 100 \
--group-by-user-session "GroupByClientAddr" \
--action Block
# Add match condition (match all requests)
az network application-gateway waf-policy custom-rule match-condition add \
--policy-name <appgw-waf-policy-name> \
--resource-group <resource-group-name> \
--name RateLimitByClientIP \
--match-variables RequestUri \
--operator Any
2.3 Create a Rate Limit Rule for a Specific Path¶
For more targeted protection, create a rate limit rule for sensitive endpoints like login pages:
- Add another custom rule:
| Setting | Value |
|---|---|
| Name | RateLimitLoginEndpoint |
| Priority | 90 |
| Rule type | Rate limit |
| Rate limit duration | 1 minute |
| Rate limit threshold | 20 |
| Group by | Client address |
- Match condition:
| Setting | Value |
|---|---|
| Match variable | RequestUri |
| Operator | Contains |
| Match values | /login, /signin, /auth |
- Action: Block
# CLI alternative
az network application-gateway waf-policy custom-rule create \
--policy-name <appgw-waf-policy-name> \
--resource-group <resource-group-name> \
--name RateLimitLoginEndpoint \
--priority 90 \
--rule-type RateLimitRule \
--rate-limit-duration OneMin \
--rate-limit-threshold 20 \
--group-by-user-session "GroupByClientAddr" \
--action Block
az network application-gateway waf-policy custom-rule match-condition add \
--policy-name <appgw-waf-policy-name> \
--resource-group <resource-group-name> \
--name RateLimitLoginEndpoint \
--match-variables RequestUri \
--operator Contains \
--values "/login" "/signin" "/auth"
2.4 Verify Rules in Portal¶
-
Navigate to Custom rules in the WAF policy.
-
Confirm both rules are listed:
| Priority | Name | Type | Threshold | Action |
|---|---|---|---|---|
| 90 | RateLimitLoginEndpoint | Rate limit | 20/min | Block |
| 100 | RateLimitByClientIP | Rate limit | 100/min | Block |
Section 3: Create Rate Limit Rule with XFF (Front Door)¶
When clients connect through Front Door, the original client IP is in the X-Forwarded-For header. Standard socket-based grouping would see all traffic as coming from Front Door's IP. Use XFF-based grouping for accurate per-client rate limiting.
3.1 Create via Azure Portal¶
-
Navigate to the Front Door WAF Policy (e.g.,
wafpolicyfd). -
Click Custom rules → + Add custom rule.
-
Configure the rate limit rule:
| Setting | Value |
|---|---|
| Custom rule name | RateLimitByXFF |
| Priority | 100 |
| Rule type | Rate limit |
| Rate limit duration | 1 minute |
| Rate limit threshold (requests) | 50 |
- Add a match condition:
| Setting | Value |
|---|---|
| Match type | String |
| Match variable | SocketAddr |
| Operator | Any |
📝 Note: Front Door uses
SocketAddrwith additional configuration for XFF-based grouping. The SocketAddr in Front Door context can be configured to parse the X-Forwarded-For header.
-
Set the Action to Block.
-
Click Add and Save.
3.2 Create via Azure CLI¶
# Create rate limit rule on Front Door WAF policy
az network front-door waf-policy rule create \
--policy-name <fd-waf-policy-name> \
--resource-group <resource-group-name> \
--name RateLimitByXFF \
--priority 100 \
--rule-type RateLimitRule \
--rate-limit-duration-in-minutes 1 \
--rate-limit-threshold 50 \
--action Block \
--defer
# Add match condition
az network front-door waf-policy rule match-condition add \
--policy-name <fd-waf-policy-name> \
--resource-group <resource-group-name> \
--name RateLimitByXFF \
--match-variable SocketAddr \
--operator Any
3.3 Create Path-Specific Rate Limit on Front Door¶
# Rate limit API endpoints more aggressively
az network front-door waf-policy rule create \
--policy-name <fd-waf-policy-name> \
--resource-group <resource-group-name> \
--name RateLimitAPI \
--priority 90 \
--rule-type RateLimitRule \
--rate-limit-duration-in-minutes 1 \
--rate-limit-threshold 30 \
--action Block \
--defer
az network front-door waf-policy rule match-condition add \
--policy-name <fd-waf-policy-name> \
--resource-group <resource-group-name> \
--name RateLimitAPI \
--match-variable RequestUri \
--operator BeginsWith \
--values "/api/"
3.4 Verify Front Door Custom Rules¶
az network front-door waf-policy show \
--name <fd-waf-policy-name> \
--resource-group <resource-group-name> \
--query "customRules.rules[].{Name:name, Priority:priority, RuleType:ruleType, Threshold:rateLimitThreshold, Action:action}" \
--output table
Section 4: Test Rate Limiting¶
4.1 Test Against Application Gateway¶
- Navigate to the scripts directory:
- Generate burst traffic that exceeds the rate limit:
Script: generate-traffic.ps1
.\generate-traffic.ps1 -TargetUrl "https://<appgw-public-ip>" -AttackType RateLimit -Count 200
```
This sends 200 requests in rapid succession, which should exceed the 100 requests/minute threshold.
### 4.2 Test Against Front Door
1. Generate burst traffic against Front Door:
```powershell
.\generate-traffic.ps1 -TargetUrl "https://<endpoint>.azurefd.net" -AttackType RateLimit -Count 200
```
### 4.3 Manual Rate Limit Testing
If the `generate-traffic.ps1` script does not have a `RateLimit` attack type, use this PowerShell script:
```powershell
$targetUrl = "https://<appgw-public-ip>/"
$totalRequests = 200
$results = @{ "200" = 0; "403" = 0; "429" = 0; "Other" = 0 }
$rateLimitHitAt = $null
Write-Host "=== Rate Limit Test ===" -ForegroundColor Cyan
Write-Host "Target: $targetUrl"
Write-Host "Sending $totalRequests requests..."
Write-Host ""
for ($i = 1; $i -le $totalRequests; $i++) {
try {
$response = Invoke-WebRequest -Uri $targetUrl -Method GET `
-SkipCertificateCheck -ErrorAction Stop -TimeoutSec 10
$statusCode = $response.StatusCode.ToString()
} catch {
$statusCode = $_.Exception.Response.StatusCode.Value__.ToString()
if (-not $statusCode) { $statusCode = "Error" }
}
if ($results.ContainsKey($statusCode)) {
$results[$statusCode]++
} else {
$results["Other"]++
}
if (($statusCode -eq "403" -or $statusCode -eq "429") -and -not $rateLimitHitAt) {
$rateLimitHitAt = $i
Write-Host " ⚠️ Rate limit triggered at request #$i" -ForegroundColor Yellow
}
# Progress indicator every 25 requests
if ($i % 25 -eq 0) {
Write-Host " Sent $i / $totalRequests requests..." -ForegroundColor Gray
}
}
Write-Host ""
Write-Host "=== Results ===" -ForegroundColor Cyan
Write-Host " 200 OK: $($results['200'])" -ForegroundColor Green
Write-Host " 403 Forbidden: $($results['403'])" -ForegroundColor Red
Write-Host " 429 Too Many: $($results['429'])" -ForegroundColor Red
Write-Host " Other: $($results['Other'])" -ForegroundColor Yellow
if ($rateLimitHitAt) {
Write-Host " Rate limit first hit at request #$rateLimitHitAt" -ForegroundColor Yellow
}
4.4 Test Login Endpoint Rate Limit¶
$targetUrl = "https://<appgw-public-ip>/login"
$totalRequests = 50
Write-Host "=== Login Rate Limit Test ===" -ForegroundColor Cyan
Write-Host "Target: $targetUrl (limit: 20/min)"
for ($i = 1; $i -le $totalRequests; $i++) {
try {
$response = Invoke-WebRequest -Uri $targetUrl -Method POST `
-Body @{username="test"; password="test"} `
-SkipCertificateCheck -ErrorAction Stop
Write-Host " Request #$i : $($response.StatusCode)" -ForegroundColor Green
} catch {
$status = $_.Exception.Response.StatusCode.Value__
Write-Host " Request #$i : $status — Rate Limited!" -ForegroundColor Red
}
}
Section 5: Verify Rate Limiting Triggers¶
5.1 Check Response Codes¶
After running the burst traffic, review the results:
- Requests 1-100: Should return 200 OK (within threshold)
- Requests 101+: Should return 403 Forbidden or 429 Too Many Requests (rate limited)
📝 Note: The exact request number where blocking starts may vary slightly due to the time window calculations and request processing time.
5.2 Verify via Application Gateway Metrics¶
-
In the Azure Portal, navigate to your Application Gateway resource.
-
Click Metrics in the left-hand menu.
-
Add the metric: Web Application Firewall Total Rule Distribution
- Filter by Rule ID matching your rate limit rule
-
Filter by Action =
Blocked -
Set the time range to Last 30 minutes.
-
You should see a count of blocked requests corresponding to the burst traffic.
5.3 Verify via Front Door Metrics¶
-
Navigate to your Front Door profile.
-
Click Metrics.
-
Add the metric: Web Application Firewall Request Count
- Filter by PolicyName
-
Split by Action
-
You should see a split between
AllowandBlockactions.
Section 6: Analyze Rate Limit Logs¶
6.1 Application Gateway Rate Limit Logs¶
AzureDiagnostics
| where ResourceType == "APPLICATIONGATEWAYS"
| where Category == "ApplicationGatewayFirewallLog"
| where TimeGenerated > ago(1h)
| where ruleId_s in ("RateLimitByClientIP", "RateLimitLoginEndpoint")
or message_s contains "rate limit"
| project
TimeGenerated,
clientIp_s,
requestUri_s,
ruleId_s,
action_s,
message_s,
details_message_s
| order by TimeGenerated desc
| take 50
6.2 Rate Limit Events Over Time¶
AzureDiagnostics
| where ResourceType == "APPLICATIONGATEWAYS"
| where Category == "ApplicationGatewayFirewallLog"
| where ruleId_s in ("RateLimitByClientIP", "RateLimitLoginEndpoint")
| where TimeGenerated > ago(1h)
| summarize
AllowedCount = countif(action_s == "Allowed" or action_s == "Matched"),
BlockedCount = countif(action_s == "Blocked")
by bin(TimeGenerated, 1m)
| render timechart
6.3 Front Door Rate Limit Logs¶
AzureDiagnostics
| where Category == "FrontDoorWebApplicationFirewallLog"
| where TimeGenerated > ago(1h)
| where ruleName_s in ("RateLimitByXFF", "RateLimitAPI")
or details_msg_s contains "rate limit"
| project
TimeGenerated,
clientIP_s,
requestUri_s,
ruleName_s,
action_s,
details_msg_s,
trackingReference_s
| order by TimeGenerated desc
| take 50
6.4 Rate Limit Impact Analysis¶
// Analyze how many requests were rate-limited per client
AzureDiagnostics
| where ResourceType == "APPLICATIONGATEWAYS"
| where Category == "ApplicationGatewayFirewallLog"
| where ruleId_s contains "RateLimit"
| where TimeGenerated > ago(1h)
| summarize
TotalRequests = count(),
AllowedRequests = countif(action_s != "Blocked"),
BlockedRequests = countif(action_s == "Blocked"),
FirstBlocked = minif(TimeGenerated, action_s == "Blocked"),
LastBlocked = maxif(TimeGenerated, action_s == "Blocked")
by clientIp_s
| extend BlockRate = round(100.0 * BlockedRequests / TotalRequests, 1)
| order by BlockedRequests desc
6.5 Top Rate-Limited IPs¶
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog" or Category == "FrontDoorWebApplicationFirewallLog"
| where TimeGenerated > ago(1h)
| where action_s == "Blocked"
| where ruleId_s contains "RateLimit" or ruleName_s contains "RateLimit"
| summarize BlockedCount = count() by clientIp_s
| order by BlockedCount desc
| take 10
| render barchart
Section 7: Advanced: Geo-based Rate Limiting¶
Configure different rate limits for different geographic regions.
7.1 Create Geo-based Rate Limit Rule (Application Gateway)¶
Allow higher limits for your primary market and stricter limits for other regions:
-
Navigate to Custom rules in the Application Gateway WAF policy.
-
Click + Add custom rule.
-
Configure:
| Setting | Value |
|---|---|
| Name | RateLimitNonDomestic |
| Priority | 80 |
| Rule type | Rate limit |
| Duration | 1 minute |
| Threshold | 30 |
| Group by | Client address |
- Match condition:
| Setting | Value |
|---|---|
| Match variable | RemoteAddr |
| Operator | GeoMatch |
| Match values | Select countries outside your primary market (e.g., exclude US, UK, DE) |
| Negate | Yes (this inverts the condition to match traffic NOT from your primary countries) |
- Action: Block
7.2 Create via Azure CLI¶
# Stricter rate limit for non-domestic traffic
az network application-gateway waf-policy custom-rule create \
--policy-name <appgw-waf-policy-name> \
--resource-group <resource-group-name> \
--name RateLimitNonDomestic \
--priority 80 \
--rule-type RateLimitRule \
--rate-limit-duration OneMin \
--rate-limit-threshold 30 \
--group-by-user-session "GroupByClientAddr" \
--action Block
# Match traffic NOT from specified countries (negate the geo condition)
az network application-gateway waf-policy custom-rule match-condition add \
--policy-name <appgw-waf-policy-name> \
--resource-group <resource-group-name> \
--name RateLimitNonDomestic \
--match-variables RemoteAddr \
--operator GeoMatch \
--values US GB DE FR \
--negate true
7.3 Front Door Geo-based Rate Limiting¶
# Geo-based rate limit on Front Door
az network front-door waf-policy rule create \
--policy-name <fd-waf-policy-name> \
--resource-group <resource-group-name> \
--name GeoRateLimit \
--priority 80 \
--rule-type RateLimitRule \
--rate-limit-duration-in-minutes 1 \
--rate-limit-threshold 30 \
--action Block \
--defer
az network front-door waf-policy rule match-condition add \
--policy-name <fd-waf-policy-name> \
--resource-group <resource-group-name> \
--name GeoRateLimit \
--match-variable SocketAddr \
--operator GeoMatch \
--values US GB DE FR \
--negate
7.4 Verify Geo-based Rules¶
// Monitor geo-based rate limiting events
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where ruleId_s == "RateLimitNonDomestic"
| where TimeGenerated > ago(1h)
| summarize
BlockedCount = count(),
Countries = make_set(clientIp_s)
by bin(TimeGenerated, 5m)
| render timechart
Section 8: Rate Limiting Best Practices¶
8.1 Choosing the Right Thresholds¶
| Endpoint Type | Suggested Threshold | Window | Rationale |
|---|---|---|---|
| Homepage / general pages | 200-500 requests | 1 min | Normal browsing generates 20-50 requests/page |
| API endpoints | 50-200 requests | 1 min | API calls should be controlled |
| Login / authentication | 10-30 requests | 1 min | Brute-force protection |
| Search / query endpoints | 30-100 requests | 1 min | Prevent scraping via search |
| Static assets | 500-2000 requests | 1 min | Browsers load many assets per page |
| Webhook receivers | 100-500 requests | 1 min | Varies by integration volume |
8.2 Avoiding False Positives¶
-
Start with high thresholds — Begin with permissive limits and reduce gradually based on observed traffic patterns.
-
Monitor in Detection mode first — Use Log action before Block to understand what would be rate-limited.
# Create a rate limit rule with Log action first
az network application-gateway waf-policy custom-rule create \
--policy-name <appgw-waf-policy-name> \
--resource-group <resource-group-name> \
--name RateLimitTest \
--priority 200 \
--rule-type RateLimitRule \
--rate-limit-duration OneMin \
--rate-limit-threshold 50 \
--group-by-user-session "GroupByClientAddr" \
--action Log
- Exclude known high-traffic sources — Add allow rules for:
- Health check probes
- Monitoring services
- Partner API integrations
-
Internal services
-
Consider NAT and shared IPs — Corporate networks and mobile carriers may share a single IP among many users. Use XFF-based grouping when possible.
-
Use path-specific rules — Apply stricter limits to sensitive endpoints (login, API) and looser limits to general content.
8.3 Rate Limiting Rule Priority Guide¶
Priority 10: AllowHealthChecks → Allow (bypass rate limiting)
Priority 20: AllowMonitoringTools → Allow
Priority 80: RateLimitNonDomestic → Block (30/min for non-primary countries)
Priority 90: RateLimitLoginEndpoint → Block (20/min for login pages)
Priority 100: RateLimitByClientIP → Block (100/min global)
Priority 200: (Managed rules) → Various
8.4 Monitoring and Alerting¶
Set up alerts for sustained rate limiting:
// Alert: High rate of rate-limited requests
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where ruleId_s contains "RateLimit"
| where action_s == "Blocked"
| where TimeGenerated > ago(15m)
| summarize RateLimitedCount = count() by clientIp_s, bin(TimeGenerated, 5m)
| where RateLimitedCount > 500
To configure this as an Azure Monitor alert rule:
- Navigate to Azure Monitor → Alerts → + New alert rule.
- Scope: Your Log Analytics workspace.
- Condition: Custom log search using the KQL query above.
- Threshold: Alert when result count > 0.
- Evaluation frequency: Every 5 minutes.
- Actions: Email, SMS, or webhook notification.
8.5 Common Rate Limiting Mistakes¶
| Mistake | Impact | Solution |
|---|---|---|
| Threshold too low | Legitimate users blocked | Start high, reduce gradually |
| No path differentiation | Static assets trigger limits | Create path-specific rules |
| Socket-based grouping behind CDN | All traffic appears as one client | Use XFF-based grouping |
| No allow-list for health probes | Load balancer marks backend unhealthy | Add allow rules for health endpoints |
| Same limit for all geo regions | Under-protection or over-blocking | Use geo-based tiered limits |
| No monitoring | Cannot detect false positives | Set up alerts and dashboards |
8.6 Combining Rate Limiting with Other WAF Features¶
Rate limiting is most effective when combined with other WAF capabilities:
Layer 1: Bot Protection (Lab 07)
└─ Block known bad bots before they consume rate limit capacity
Layer 2: Rate Limiting (this lab)
└─ Limit request volume from individual sources
Layer 3: Managed Rules (Labs 02-05)
└─ Inspect allowed requests for attack payloads
Layer 4: Custom Rules
└─ Application-specific logic and restrictions
Summary¶
In this lab, you:
- ✅ Understood rate limiting concepts including thresholds, time windows, and group-by keys
- ✅ Created a rate limit rule on Application Gateway using client IP grouping
- ✅ Created a rate limit rule on Front Door using XFF-based grouping
- ✅ Tested rate limiting by sending burst traffic exceeding thresholds
- ✅ Verified rate limiting triggers through response codes and metrics
- ✅ Analyzed rate limit logs using KQL queries
- ✅ Configured advanced geo-based rate limiting rules
- ✅ Learned rate limiting best practices for production deployments
Workshop Completion¶
Congratulations! You have completed the Azure WAF Workshop. Here is a summary of what you accomplished across all labs:
| Lab | Topic | Key Skills |
|---|---|---|
| Lab 05 | Prevention Mode | Switched Detection → Prevention, validated blocking |
| Lab 06 | Front Door WAF | Edge WAF, origin lockdown, multi-layer protection |
| Lab 07 | Bot Protection | Bot Manager, JavaScript Challenge, bot categorization |
| Lab 08 | Rate Limiting | Threshold configuration, XFF grouping, geo-based limits |
Recommended next steps: - Review and clean up workshop resources to avoid unnecessary Azure costs - Apply these patterns to your production WAF deployments - Set up Azure Monitor alerts for WAF events - Schedule regular WAF rule reviews and tuning sessions