Front Door
Introduction
Section titled “Introduction”Azure Front Door is a global content delivery network and load balancer that routes client traffic to the fastest available origin using Microsoft’s global network.
It combines HTTP load balancing, SSL offloading, URL-based routing, a rules engine, caching and a Web Application Firewall into a single entry point for your web applications.
Front Door Standard and Premium profiles are configured through the Microsoft.Cdn resource provider and managed with the az afd CLI command group; WAF policies live under Microsoft.Network. For more information, see What is Azure Front Door?.
LocalStack for Azure emulates both the Front Door control plane and its data plane: you create profiles, endpoints, origins, routes, rules and WAF policies with the same APIs, CLI commands and templates you use on Azure, and then send HTTP requests to your endpoint’s hostname and have them routed, filtered, cached and forwarded to your origin locally. Where a behaviour is approximated rather than reproduced, the Limitations section says which way it differs.
The supported APIs are available on our API Coverage section, which lists every operation of the Microsoft.Cdn resource provider.
Getting started
Section titled “Getting started”This guide is designed for users new to Azure Front Door and assumes basic knowledge of the Azure CLI and our lstk az proxy.
Launch LocalStack using your preferred method. For more information, see Introduction to LocalStack for Azure. Once the container is running, enable Azure CLI interception by running:
lstk az start-interceptionThis command points the az CLI away from the public Azure management REST API and toward the LocalStack for Azure emulator API.
To revert this configuration, run:
lstk az stop-interceptionThis reconfigures the az CLI to send commands to the official Azure management REST API.
Start a local origin
Section titled “Start a local origin”Front Door forwards requests to an origin, so start something for it to forward to. This serves a single file on port 8080:
mkdir origin && echo "hello from the origin" > origin/index.htmlpython3 -m http.server 8080 --directory originLeave it running in its own terminal.
Create a resource group
Section titled “Create a resource group”Create a resource group to hold your Front Door resources:
az group create \ --name rg-afd-demo \ --location westeurope{ "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-afd-demo", "location": "westeurope", "managedBy": null, "name": "rg-afd-demo", "properties": { "provisioningState": "Succeeded" }, "tags": null, "type": "Microsoft.Resources/resourceGroups"}Create a Front Door profile
Section titled “Create a Front Door profile”Create a Front Door Standard profile to serve as the top-level container for all Front Door resources:
az afd profile create \ --resource-group rg-afd-demo \ --profile-name afd-demo \ --sku Standard_AzureFrontDoor{ "extendedProperties": {}, "frontDoorId": "eb208b8150434ce58740d20a1749434d", "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg-afd-demo/providers/Microsoft.Cdn/profiles/afd-demo", "kind": "frontdoor", "location": "Global", "name": "afd-demo", "originResponseTimeoutSeconds": 30, "provisioningState": "Succeeded", "resourceGroup": "rg-afd-demo", "resourceState": "Active", "sku": { "name": "Standard_AzureFrontDoor" }, "tags": {}, "type": "Microsoft.Cdn/profiles"}The frontDoorId is minted once per profile and is the value your origin receives in the X-Azure-FDID request header.
Create an endpoint
Section titled “Create an endpoint”Create an endpoint to expose a publicly accessible hostname for your Front Door profile:
az afd endpoint create \ --resource-group rg-afd-demo \ --profile-name afd-demo \ --endpoint-name my-endpoint \ --enabled-state Enabled{ "deploymentStatus": "NotStarted", "enabledState": "Enabled", "hostName": "my-endpoint-3tu2ya09zoh8c401.z01.azurefd.net", "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg-afd-demo/providers/Microsoft.Cdn/profiles/afd-demo/afdendpoints/my-endpoint", "location": "Global", "name": "my-endpoint", "provisioningState": "Succeeded", "resourceGroup": "rg-afd-demo", "tags": {}, "type": "Microsoft.Cdn/profiles/afdendpoints"}The hostName field contains the generated *.z01.azurefd.net domain assigned to this endpoint, in the same shape Azure mints.
That name does not resolve to your machine, so the emulator also serves the endpoint on a development alias — see Reaching an endpoint locally.
Create an origin group
Section titled “Create an origin group”Create an origin group to define the set of backend servers that Front Door will load-balance across, including a health probe configuration:
az afd origin-group create \ --resource-group rg-afd-demo \ --profile-name afd-demo \ --origin-group-name my-origin-group \ --probe-path "/" \ --probe-request-type GET \ --probe-protocol Http \ --probe-interval-in-seconds 120 \ --sample-size 2 \ --successful-samples-required 2{ "deploymentStatus": "NotStarted", "healthProbeSettings": { "probeIntervalInSeconds": 120, "probePath": "/", "probeProtocol": "Http", "probeRequestType": "GET" }, "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg-afd-demo/providers/Microsoft.Cdn/profiles/afd-demo/origingroups/my-origin-group", "loadBalancingSettings": { "additionalLatencyInMilliseconds": 0, "sampleSize": 2, "successfulSamplesRequired": 2 }, "name": "my-origin-group", "provisioningState": "Succeeded", "resourceGroup": "rg-afd-demo", "sessionAffinityState": "Disabled", "type": "Microsoft.Cdn/profiles/origingroups"}Load-balancing settings are required, as on Azure: an origin group without them is refused with Property 'AfdOriginGroup.LoadBalancingSettings' is required but it was not set.
Add an origin
Section titled “Add an origin”Add an origin pointing at the local server you started.
localhost.localstack.cloud resolves to 127.0.0.1, which is where the origin listens when the emulator runs on your machine:
az afd origin create \ --resource-group rg-afd-demo \ --profile-name afd-demo \ --origin-group-name my-origin-group \ --origin-name my-origin \ --host-name localhost.localstack.cloud \ --http-port 8080 \ --https-port 443 \ --enforce-certificate-name-check false{ "deploymentStatus": "NotStarted", "enabledState": "Enabled", "enforceCertificateNameCheck": false, "hostName": "localhost.localstack.cloud", "httpPort": 8080, "httpsPort": 443, "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg-afd-demo/providers/Microsoft.Cdn/profiles/afd-demo/origingroups/my-origin-group/origins/my-origin", "name": "my-origin", "originGroupName": "my-origin-group", "priority": 1, "provisioningState": "Succeeded", "resourceGroup": "rg-afd-demo", "type": "Microsoft.Cdn/profiles/origingroups/origins", "weight": 50}Create a route
Section titled “Create a route”Create a route to wire the endpoint to the origin group and define which protocols and path patterns are accepted.
--origin-group accepts either the origin group’s name within the profile or its full ARM resource ID.
The route forwards over HTTP only because the local origin serves plain HTTP:
az afd route create \ --resource-group rg-afd-demo \ --profile-name afd-demo \ --endpoint-name my-endpoint \ --route-name my-route \ --origin-group my-origin-group \ --supported-protocols Http Https \ --link-to-default-domain Enabled \ --https-redirect Disabled \ --forwarding-protocol HttpOnly{ "customDomains": [], "deploymentStatus": "NotStarted", "enabledState": "Enabled", "forwardingProtocol": "HttpOnly", "httpsRedirect": "Disabled", "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg-afd-demo/providers/Microsoft.Cdn/profiles/afd-demo/afdendpoints/my-endpoint/routes/my-route", "linkToDefaultDomain": "Enabled", "name": "my-route", "originGroup": { "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-afd-demo/providers/Microsoft.Cdn/profiles/afd-demo/originGroups/my-origin-group", "resourceGroup": "rg-afd-demo" }, "patternsToMatch": [ "/*" ], "provisioningState": "Succeeded", "resourceGroup": "rg-afd-demo", "ruleSets": [], "supportedProtocols": [ "Http", "Https" ], "type": "Microsoft.Cdn/profiles/afdendpoints/routes"}A route’s origin group must already contain at least one enabled origin; Azure refuses the route otherwise, and so does LocalStack.
Create a rule set and a rule
Section titled “Create a rule set and a rule”Create a rule set to group routing rules under the profile:
az afd rule-set create \ --resource-group rg-afd-demo \ --profile-name afd-demo \ --rule-set-name myruleset{ "deploymentStatus": "NotStarted", "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg-afd-demo/providers/Microsoft.Cdn/profiles/afd-demo/rulesets/myruleset", "name": "myruleset", "provisioningState": "Succeeded", "resourceGroup": "rg-afd-demo", "type": "Microsoft.Cdn/profiles/rulesets"}Add a rule that appends a response header.
A rule must carry at least one action — Azure refuses Rule 'myrule' must have at least one action. for one that does not, and LocalStack answers the same — while conditions are optional, and a rule without any matches every request:
az afd rule create \ --resource-group rg-afd-demo \ --profile-name afd-demo \ --rule-set-name myruleset \ --rule-name myrule \ --order 1 \ --match-processing-behavior Continue \ --actions "[{modify-response-header:{parameters:{header-action:Append,header-name:X-Served-By,value:LocalStack}}}]"{ "actions": [ { "name": "ModifyResponseHeader", "parameters": { "headerAction": "Append", "headerName": "X-Served-By", "typeName": "DeliveryRuleHeaderActionParameters", "value": "LocalStack" } } ], "conditions": [], "deploymentStatus": "NotStarted", "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg-afd-demo/providers/Microsoft.Cdn/profiles/afd-demo/rulesets/myruleset/rules/myrule", "matchProcessingBehavior": "Continue", "name": "myrule", "order": 1, "provisioningState": "Succeeded", "resourceGroup": "rg-afd-demo", "ruleSetName": "myruleset", "type": "Microsoft.Cdn/profiles/rulesets/rules"}Attach the rule set to the route so the rule runs on its traffic:
RULE_SET_ID=$(az afd rule-set show \ --resource-group rg-afd-demo \ --profile-name afd-demo \ --rule-set-name myruleset \ --query id \ --output tsv)
az afd route update \ --resource-group rg-afd-demo \ --profile-name afd-demo \ --endpoint-name my-endpoint \ --route-name my-route \ --formatted-rule-sets "[{id:'$RULE_SET_ID'}]"{ "customDomains": [], "deploymentStatus": "NotStarted", "enabledState": "Enabled", "forwardingProtocol": "HttpOnly", "httpsRedirect": "Disabled", "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg-afd-demo/providers/Microsoft.Cdn/profiles/afd-demo/afdendpoints/my-endpoint/routes/my-route", "linkToDefaultDomain": "Enabled", "name": "my-route", "originGroup": { "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-afd-demo/providers/Microsoft.Cdn/profiles/afd-demo/originGroups/my-origin-group", "resourceGroup": "rg-afd-demo" }, "patternsToMatch": [ "/*" ], "provisioningState": "Succeeded", "resourceGroup": "rg-afd-demo", "ruleSets": [ { "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-afd-demo/providers/Microsoft.Cdn/profiles/afd-demo/ruleSets/myruleset", "resourceGroup": "rg-afd-demo" } ], "supportedProtocols": [ "Http", "Https" ], "type": "Microsoft.Cdn/profiles/afdendpoints/routes"}Send a request through Front Door
Section titled “Send a request through Front Door”Request the file through the endpoint’s development alias.
The response comes from your local origin, carries the header the rule appended, and the X-Azure-Ref and X-Cache headers Front Door adds:
curl -i http://my-endpoint.afd.azure.localhost.localstack.cloud:4566/index.htmlHTTP/1.1 200 OKServer: TwistedWeb/26.4.0Date: Fri, 18 Sep 2026 12:24:25 GMTContent-type: text/htmlLast-Modified: Fri, 18 Sep 2026 12:24:15 GMTX-Served-By: LocalStackX-Azure-Ref: 2a4085a7a988bb64a77ba963791d02f4X-Cache: CONFIG_NOCACHEContent-Length: 22
hello from the originX-Cache is CONFIG_NOCACHE because this route has no cache configuration. A route created with --cache-configuration "{query-string-caching-behavior:IgnoreQueryString}" answers X-Cache: MISS on the first request and X-Cache: HIT (with an Age header) on the next. Purging empties the cache, and the request after it is a MISS again:
az afd endpoint purge \ --resource-group rg-afd-demo \ --profile-name afd-demo \ --endpoint-name my-endpoint \ --content-paths "/*"On the origin’s side, the request arrives with the headers Front Door adds: X-Azure-FDID (the profile’s frontDoorId), X-Azure-ClientIP, X-Azure-SocketIP, X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto and Via: 1.1 Azure.
Show and list
Section titled “Show and list”Show the Front Door profile to inspect its current state:
az afd profile show \ --resource-group rg-afd-demo \ --profile-name afd-demo{ "extendedProperties": {}, "frontDoorId": "eb208b8150434ce58740d20a1749434d", "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg-afd-demo/providers/Microsoft.Cdn/profiles/afd-demo", "kind": "frontdoor", "location": "Global", "name": "afd-demo", "originResponseTimeoutSeconds": 30, "provisioningState": "Succeeded", "resourceGroup": "rg-afd-demo", "resourceState": "Active", "sku": { "name": "Standard_AzureFrontDoor" }, "tags": {}, "type": "Microsoft.Cdn/profiles"}List all profiles in the resource group:
az afd profile list --resource-group rg-afd-demo[ { "extendedProperties": {}, "frontDoorId": "eb208b8150434ce58740d20a1749434d", "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg-afd-demo/providers/Microsoft.Cdn/profiles/afd-demo", "kind": "frontdoor", "location": "Global", "name": "afd-demo", "originResponseTimeoutSeconds": 30, "provisioningState": "Succeeded", "resourceGroup": "rg-afd-demo", "resourceState": "Active", "sku": { "name": "Standard_AzureFrontDoor" }, "tags": {}, "type": "Microsoft.Cdn/profiles" }]Delete and verify
Section titled “Delete and verify”Delete the Front Door profile to remove the profile and all child resources:
az afd profile delete \ --resource-group rg-afd-demo \ --profile-name afd-demoVerify the resource group no longer contains any profiles:
az afd profile list --resource-group rg-afd-demo[]Reaching an endpoint locally
Section titled “Reaching an endpoint locally”Every endpoint gets a *.z01.azurefd.net hostname like Azure’s.
That name does not resolve to your machine, so the emulator also serves each endpoint on a development alias:
http://<endpoint-name>.afd.azure.localhost.localstack.cloud:4566/*.localhost.localstack.cloud resolves to 127.0.0.1 publicly, so this works without any DNS setup.
Custom domains are served on their own hostname once they are validated (or immediately, with AFD_DOMAIN_AUTO_APPROVE=1), provided that hostname resolves to the emulator.
Features
Section titled “Features”- The complete
Microsoft.Cdncontrol plane. All 138 operations of the resource provider are implemented: profiles, endpoints, origin groups, origins, routes, rule sets and rules, custom domains, secrets, security policies, purge, Log Analytics, resource usage, name availability, the classic CDN surface, migration and Edge Actions. The API coverage table below is generated from the emulator. - Front Door resources under
Microsoft.Network. The 38 operations for Front Door WAF policies (frontDoorWebApplicationFirewallPolicies), classic Front Door (frontDoors) and Internet Analyzer (networkExperimentProfiles) are implemented and listed on the Microsoft.Network coverage. - A working data plane. Requests to an endpoint hostname go through WAF evaluation, route matching (the most specific pattern wins), the rules engine, the cache, origin selection by priority, weight and health, forwarding, and the access log — for Standard/Premium profiles and for classic Front Doors alike.
- Rules engine. All 19 match conditions, 9 actions and the server variables are evaluated on every request;
RegExoperators are validated on create against the constructs Azure accepts. - Web Application Firewall. Custom rules with every operator and match variable, the managed rule sets (
Microsoft_DefaultRuleSet1.1–2.1 andMicrosoft_BotManagerRuleSet) with anomaly scoring, Prevention and Detection modes, and security policies that bind a WAF policy to endpoints and custom domains. - Caching and compression. Route-level caching honours the query-string behaviour and cache keys, repeat requests are served from the edge,
az afd endpoint purgeinvalidates by path, and compression applies when the route asks for it and the client accepts it. - Forwarding headers as Azure sends them. The origin receives
X-Azure-FDID,X-Azure-ClientIP,X-Azure-SocketIP,X-Azure-RequestChain,X-Forwarded-For(with the socket IP appended),X-Forwarded-Host,X-Forwarded-ProtoandVia; reservedX-FD-*andX-Azure-*headers sent by a client are removed, as on Azure. Health probes identify themselves withX-FD-HealthProbe: 1. - Custom domains with the real validation state machine. A new domain lands in
Pendingwith a validation token, as on Azure. TLS settings are validated on create and on update:TLS10andTLS13are refused andcipherSuiteSetTypedefaults toTLS12_2022. - Azure’s refusals, in Azure’s words. LocalStack rejects what Azure rejects — a rule without an action, an origin group without load-balancing settings, a route whose origin group has no enabled origin, a URL-signing secret without a version, a WAF report on a Standard profile — with the messages recorded from the real service.
- Log Analytics. All six operations aggregate the emulator’s own access log; a profile that has served no traffic answers empty series, as on Azure.
Configuration
Section titled “Configuration”| Variable | Default | Effect |
|---|---|---|
AFD_DOMAIN_AUTO_APPROVE |
unset | Approve a custom domain on creation instead of leaving it Pending until a _dnsauth TXT record is seen. Off by default so responses match Azure. |
CDN_DEBUG_HEADERS |
unset | Add an X-AFD-Diag response header explaining the routing decision. Azure does not send it. |
CDN_VALIDATE_PROBE_FETCHES |
unset | Make ValidateProbe fetch the URL. Off by default: the emulator answers EndpointProbeCannotBeRetrieved, which is what Azure answers for a URL only reachable from your machine. |
CDN_CLASSIC_ALLOW_CREATE |
unset | Allow creating classic CDN profiles and endpoints, which Azure has refused since 2025-08-15. |
FRONT_DOOR_CLASSIC_ALLOW_CREATE |
unset | Allow creating classic Microsoft.Network/frontDoors, which Azure has refused since 2025-04-01. |
CDN_DEFAULT_CLIENT_COUNTRY |
US |
Country reported for every request, since the emulator has no geolocation database. Override per request with the X-LocalStack-Client-Country header. |
CDN_CACHE_MAX_ENTRIES |
1000 |
Entries the edge cache holds before evicting the least recently used. |
CDN_CACHE_MAX_BODY_BYTES |
8388608 |
Largest response body the cache stores. |
CDN_ACCESS_LOG_MAX_ENTRIES |
5000 |
Access-log entries kept for the Log Analytics operations. |
Limitations
Section titled “Limitations”- Classic resources cannot be created by default. Azure no longer accepts new classic CDN profiles (
Azure CDN from Microsoft (classic) no longer support new profile creation.) or classic Front Doors, and LocalStack reproduces the refusal so a template that fails on Azure fails locally. Set the flags above to work with the classic surfaces anyway; every classic operation is emulated once creation is allowed. - No geolocation.
GeoMatchuses a fixed country unless the request setsX-LocalStack-Client-Country, so a caller can choose its country locally, which it never can on Azure. - Managed WAF rules are approximations. Microsoft does not publish the signatures behind its managed rule sets, so LocalStack matches the documented attack classes of each rule. It errs towards missing an attack rather than blocking legitimate traffic.
- Origin certificates are not verified.
enforceCertificateNameCheckis stored and returned but has no effect; a self-signed local origin is served. - No certificate authority. Managed-certificate and bring-your-own-certificate flows reach their terminal state immediately rather than being issued over time.
- Edge Actions code is stored and validated but not executed.
- Internet Analyzer results are synthetic. Scorecards and timeseries are derived deterministically and say so in their
description. X-Azure-JA4-Fingerprintis not sent to origins: it derives from the TLS handshake, which the emulator does not see.X-Cachereports the access-log vocabulary. LocalStack answersHIT,MISSandUNCACHEABLE, which is how Azure’s access log spellscacheStatus; theX-Cacheresponse header itself usesTCP_HIT,TCP_REMOTE_HIT,TCP_MISS,PRIVATE_NOSTOREandCONFIG_NOCACHE. Match on the word rather than the whole value.
Samples
Section titled “Samples”The following sample demonstrates how to use Azure Front Door with LocalStack for Azure:
API Coverage
Section titled “API Coverage”135 of 135 operations implemented
| Operation ▲ | Implemented ▼ |
|---|