Azure Front Door vs Application Gateway: Choosing Your Entry Point
Every public web workload on Azure needs an entry point, and two services show up in almost every architecture discussion: Azure Front Door and Azure Application Gateway. Both are layer 7 load balancers, both terminate TLS, both can run a Web Application Firewall, and both route by host and path. It is easy to pick one at random, or to deploy both without a clear reason.
The distinction becomes clear once you look at where each one runs and what problem it was built to solve. This post compares them from a practical angle and shows how to configure each, how to combine them, and what your .NET application needs to handle behind either one.
The short answer
Front Door is a global service that runs at Microsoft's edge. Application Gateway is a regional service that runs inside your virtual network. Everything else follows from that.
- Choose Front Door when users are spread across regions, when you need edge caching and acceleration, or when you run the same app in several regions and want global failover.
- Choose Application Gateway when traffic must enter through your VNet, when origins are private, or when you need a regional ingress for workloads like AKS or VMs.
- Use both when you need global distribution and a private, VNet-integrated regional tier.
How Azure Front Door works
Front Door uses anycast, so clients connect to the nearest Microsoft edge location. TLS is terminated at the edge, and requests travel to your origin over the Microsoft backbone rather than the public internet for the whole distance. Front Door keeps health probes against origins and routes to the healthiest, lowest-latency origin in an origin group, which makes active-active or active-passive multi-region setups straightforward.
The current offering comes in Standard and Premium tiers. Standard covers routing, caching, custom domains with managed certificates, and custom WAF rules. Premium adds managed WAF rule sets, bot protection, and Private Link connectivity to origins, so an origin like an App Service or internal load balancer does not need to be publicly reachable. The older Front Door (classic) tier is on a retirement path; new deployments should use Standard or Premium.
How Application Gateway works
Application Gateway is deployed into a dedicated subnet in your VNet. It can expose a public frontend IP, a private frontend IP, or both. Because it lives in the VNet, it can reach backends by private IP, including VMs, VM scale sets, internal load balancers, and private endpoints.
The v2 SKUs (Standard_v2 and WAF_v2) support autoscaling, zone redundancy, URL rewrites, end-to-end TLS, multi-site hosting, and path-based routing. For Kubernetes, the Application Gateway Ingress Controller and the newer Application Gateway for Containers integrate the gateway with cluster ingress.
Side-by-side comparison
| Capability | Azure Front Door | Application Gateway |
|---|---|---|
| Scope | Global, edge network | Regional, inside your VNet |
| Client connection | Anycast to nearest edge location | Frontend IP in one region |
| Private frontend | No | Yes |
| Private origins | Premium via Private Link | Native, by private IP |
| Caching and compression | Yes, at the edge | No caching |
| WAF | Yes (managed rule sets in Premium) | Yes (WAF_v2 SKU) |
| Multi-region failover | Built in | Requires a global layer in front |
| Path and host routing | Yes | Yes |
| Header and URL rewrite | Rule sets | Rewrite rule sets |
| Protocols | HTTP/HTTPS | HTTP/HTTPS; check docs for TCP/TLS proxy support |
| Deployment unit | Profile, no subnet needed | Dedicated subnet per gateway |
Pricing models differ significantly and change over time, so model your expected traffic with the Azure pricing calculator rather than relying on rules of thumb.
Configuring Front Door with the Azure CLI
A minimal Front Door Premium setup has a profile, an endpoint, an origin group with a health probe, an origin, and a route:
az afd profile create \
--resource-group rg-web-global \
--profile-name afd-shop \
--sku Premium_AzureFrontDoor
az afd endpoint create \
--resource-group rg-web-global \
--profile-name afd-shop \
--endpoint-name shop \
--enabled-state Enabled
az afd origin-group create \
--resource-group rg-web-global \
--profile-name afd-shop \
--origin-group-name og-api \
--probe-request-type GET \
--probe-protocol Https \
--probe-path /health \
--probe-interval-in-seconds 30 \
--sample-size 4 \
--successful-samples-required 3 \
--additional-latency-in-milliseconds 50
az afd origin create \
--resource-group rg-web-global \
--profile-name afd-shop \
--origin-group-name og-api \
--origin-name sea \
--host-name app-shop-sea.azurewebsites.net \
--origin-host-header app-shop-sea.azurewebsites.net \
--priority 1 --weight 1000 \
--enabled-state Enabled \
--http-port 80 --https-port 443
az afd route create \
--resource-group rg-web-global \
--profile-name afd-shop \
--endpoint-name shop \
--route-name api \
--origin-group og-api \
--patterns-to-match "/*" \
--supported-protocols Https Http \
--forwarding-protocol HttpsOnly \
--https-redirect Enabled \
--link-to-default-domain Enabled
Add a second origin in another region with the same priority for active-active, or a higher priority number for failover. The additional-latency-in-milliseconds setting controls how close in latency origins must be to share traffic.
Health probes deserve attention
Point probes at a lightweight endpoint that reflects whether the instance can serve traffic, not at / and not at a deep dependency check that fails whenever one downstream is slow. A probe that is too strict turns a partial degradation into a full regional failover.
Configuring Application Gateway
Application Gateway needs a VNet, a dedicated subnet, and a Standard SKU public IP if you want a public frontend:
az network vnet create \
--resource-group rg-web-sea \
--name vnet-web-sea \
--address-prefixes 10.20.0.0/16 \
--subnet-name snet-appgw \
--subnet-prefixes 10.20.0.0/24
az network public-ip create \
--resource-group rg-web-sea \
--name pip-appgw-sea \
--sku Standard \
--allocation-method Static
az network application-gateway create \
--resource-group rg-web-sea \
--name agw-web-sea \
--sku WAF_v2 \
--min-capacity 2 --max-capacity 10 \
--vnet-name vnet-web-sea \
--subnet snet-appgw \
--public-ip-address pip-appgw-sea \
--servers 10.20.1.4 10.20.1.5 \
--priority 100 \
--waf-policy waf-web-sea
The WAF policy must exist before you reference it (az network application-gateway waf-policy create). Size the subnet generously; autoscaling instances consume addresses from it, and resizing a subnet in use is painful.
Combining both
A common enterprise pattern puts Front Door in front of regional Application Gateways:
Client -> Front Door (edge, global WAF, caching, failover)
-> Application Gateway per region (VNet ingress, private routing)
-> AKS / VMs / internal services
This makes sense when regional backends must stay private and you need VNet-level controls, or when a platform team owns regional ingress for many applications. It also adds cost, latency, and another hop to debug. If your origins are App Service or Container Apps, Front Door Premium with Private Link often removes the need for Application Gateway entirely.
Avoid running WAF with identical rules on both layers without a reason. Duplicate inspection means duplicate false positives and two places to tune exclusions.
Locking down the origin
Putting Front Door in front of an app is pointless if the app is still reachable directly. Front Door sends an X-Azure-FDID header containing your profile's unique ID. Combine that with the AzureFrontDoor.Backend service tag. For App Service, access restrictions support both in one rule:
FDID=$(az afd profile show -g rg-web-global --profile-name afd-shop --query frontDoorId -o tsv)
az webapp config access-restriction add \
--resource-group rg-web-sea \
--name app-shop-sea \
--rule-name allow-front-door \
--action Allow \
--priority 100 \
--service-tag AzureFrontDoor.Backend \
--http-header x-azure-fdid=$FDID
The service tag alone is not enough, because it covers every Front Door customer. The header check ties access to your profile. With Premium and Private Link, you can go further and disable public network access on the origin.
What your .NET app needs to handle
Behind either proxy, the app sees the proxy's connection, not the client's. Scheme, host, and client IP arrive in forwarded headers, and ASP.NET Core ignores them unless you opt in:
using Microsoft.AspNetCore.HttpOverrides;
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor
| ForwardedHeaders.XForwardedProto
| ForwardedHeaders.XForwardedHost;
// Proxy IPs are not fixed; rely on network lockdown instead of an allow-list.
// On .NET 10+, KnownNetworks is superseded by KnownIPNetworks.
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
options.AllowedHosts = ["shop.example.com"];
});
var app = builder.Build();
app.UseForwardedHeaders();
Without this, redirect URLs, OAuth callback URLs, and generated links use the wrong scheme or host, and logs record proxy IPs instead of client IPs. Clearing known proxies is only safe when the origin is locked down so that only your proxy can reach it; otherwise clients can spoof these headers. Also preserve the original host header where possible, since mismatched hosts break cookies and authentication redirects.
Decision guide
- Single region, public PaaS origin, global users: Front Door Standard or Premium.
- Single region, private backends in a VNet, regional users: Application Gateway WAF_v2.
- Multi-region active-active or failover: Front Door, optionally with Application Gateway per region.
- Private origins without managing a gateway subnet: Front Door Premium with Private Link.
- Kubernetes ingress with Azure-native integration: Application Gateway for Containers, fronted by Front Door if global.
Conclusion
Front Door and Application Gateway overlap in features but not in purpose. Front Door is your global front door at the edge; Application Gateway is your regional entrance inside the network. Start from where your users are and where your origins live, then add the second layer only when it solves a concrete problem. Whichever you choose, lock the origin down so the proxy cannot be bypassed, configure forwarded headers in your application, and treat health probes as a first-class part of the design.