Skip to content
Get Started for Free

Front Door

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.

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:

Terminal window
lstk az start-interception

This 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:

Terminal window
lstk az stop-interception

This reconfigures the az CLI to send commands to the official Azure management REST API.

Front Door forwards requests to an origin, so start something for it to forward to. This serves a single file on port 8080:

Terminal window
mkdir origin && echo "hello from the origin" > origin/index.html
python3 -m http.server 8080 --directory origin

Leave it running in its own terminal.

Create a resource group to hold your Front Door resources:

Terminal window
az group create \
--name rg-afd-demo \
--location westeurope
Output
{
"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 Standard profile to serve as the top-level container for all Front Door resources:

Terminal window
az afd profile create \
--resource-group rg-afd-demo \
--profile-name afd-demo \
--sku Standard_AzureFrontDoor
Output
{
"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 to expose a publicly accessible hostname for your Front Door profile:

Terminal window
az afd endpoint create \
--resource-group rg-afd-demo \
--profile-name afd-demo \
--endpoint-name my-endpoint \
--enabled-state Enabled
Output
{
"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 to define the set of backend servers that Front Door will load-balance across, including a health probe configuration:

Terminal window
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
Output
{
"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 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:

Terminal window
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
Output
{
"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 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:

Terminal window
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
Output
{
"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 to group routing rules under the profile:

Terminal window
az afd rule-set create \
--resource-group rg-afd-demo \
--profile-name afd-demo \
--rule-set-name myruleset
Output
{
"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:

Terminal window
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}}}]"
Output
{
"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:

Terminal window
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'}]"
Output
{
"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"
}

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:

Terminal window
curl -i http://my-endpoint.afd.azure.localhost.localstack.cloud:4566/index.html
Output
HTTP/1.1 200 OK
Server: TwistedWeb/26.4.0
Date: Fri, 18 Sep 2026 12:24:25 GMT
Content-type: text/html
Last-Modified: Fri, 18 Sep 2026 12:24:15 GMT
X-Served-By: LocalStack
X-Azure-Ref: 2a4085a7a988bb64a77ba963791d02f4
X-Cache: CONFIG_NOCACHE
Content-Length: 22
hello from the origin

X-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:

Terminal window
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 the Front Door profile to inspect its current state:

Terminal window
az afd profile show \
--resource-group rg-afd-demo \
--profile-name afd-demo
Output
{
"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:

Terminal window
az afd profile list --resource-group rg-afd-demo
Output
[
{
"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 the Front Door profile to remove the profile and all child resources:

Terminal window
az afd profile delete \
--resource-group rg-afd-demo \
--profile-name afd-demo

Verify the resource group no longer contains any profiles:

Terminal window
az afd profile list --resource-group rg-afd-demo
Output
[]

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.

  • The complete Microsoft.Cdn control 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; RegEx operators 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_DefaultRuleSet 1.1–2.1 and Microsoft_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 purge invalidates 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-Proto and Via; reserved X-FD-* and X-Azure-* headers sent by a client are removed, as on Azure. Health probes identify themselves with X-FD-HealthProbe: 1.
  • Custom domains with the real validation state machine. A new domain lands in Pending with a validation token, as on Azure. TLS settings are validated on create and on update: TLS10 and TLS13 are refused and cipherSuiteSetType defaults to TLS12_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.
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.
  • 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. GeoMatch uses a fixed country unless the request sets X-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. enforceCertificateNameCheck is 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-Fingerprint is not sent to origins: it derives from the TLS handshake, which the emulator does not see.
  • X-Cache reports the access-log vocabulary. LocalStack answers HIT, MISS and UNCACHEABLE, which is how Azure’s access log spells cacheStatus; the X-Cache response header itself uses TCP_HIT, TCP_REMOTE_HIT, TCP_MISS, PRIVATE_NOSTORE and CONFIG_NOCACHE. Match on the word rather than the whole value.

The following sample demonstrates how to use Azure Front Door with LocalStack for Azure:

135 of 135 operations implemented

Operation ▲Implemented ▼
Page 1 of 0
Was this page helpful?