What it is
A dedicated App Service plan (Basic, Standard, Premium v2, v3, or v4; Windows or Linux) whose 95th-percentile CPU and memory sit far below what its SKU and instance count provide. The apps on it work perfectly, which is exactly why nobody looks: an S3 plan running three instances at 15% CPU behaves identically to one that needs them, and it bills identically too. The waste is in two dimensions at once, the size of each instance and the number of them.
Why it happens
App Service plans bill per instance-hour of the chosen SKU, not per request, so the plan is a fixed cost from the moment it is created. Sizing happens once, before launch, against a guess about peak traffic that is almost always generous, and the instance count gets a "just in case" bump on top. Then the workload turns out smaller than feared, or it moves on, and the plan stays where it was because a right-sized plan and an oversized one look the same from inside the apps.
Reading the metrics is less obvious than it looks. The plan's CpuPercentage metric is the average across all instances, so 20% on a three-instance S3 plan means about 2.4 of the plan's twelve cores in real demand, roughly 0.6 of one instance. MemoryPercentage is per instance and does not shrink when you scale in, so memory is often the dimension that decides the floor, not CPU. A plan can also be prevented from shrinking by something it uses: deployment slots (Basic has none, Standard allows 5 per app), autoscale (Basic does not support it), per-app scaling, zone redundancy (two instances minimum), IP-based SSL (not on Basic), or an instance ceiling (Basic stops at 3, Standard at 10). Each of those is a reason a smaller tier is wrong, and each is one more thing to check that nobody checks.
An empty plan with zero apps is a different problem; see AZF-0012.
What it costs / blast radius
At Windows pay-as-you-go list price in East US, S3 is about $0.40/hour (~$292/month per instance) and S2 about $0.20/hour (~$146/month per instance). A plan running S3 x 3 bills about $876/month; if its P95 load fits S2 x 2 at about $292/month, the oversizing costs roughly $584/month, or about $7,000/year, for one plan. (List price; Linux rates are slightly lower, and reservations or savings plans change the math.) Many tenants have more than one plan in this state, one per team that launched something.
The move has real consequences, which is why it needs a review and not a reflex. Dropping to a single instance removes redundancy: a platform restart or instance failure takes the apps offline until it recovers. Basic, Standard, and Premium v2 run on older hardware, so per-core performance on a cheaper tier may be lower than the vCPU count suggests. Plan storage drops with the tier (Basic 10 GB, Standard 50 GB, Premium 250 GB). If autoscale manages the instance count, do not scale in by hand; a smaller SKU can simply cause autoscale to add instances back. And a target tier may not be available in the plan's current deployment unit, in which case the portal greys it out and the move needs a new plan and a redeploy.
See it
// Step 1: dedicated plans that host at least one app (empty plans are AZF-0012).
Resources
| where type =~ 'microsoft.web/serverfarms'
| where tostring(properties.computeMode) =~ 'Dedicated'
| where isnull(properties.hostingEnvironmentProfile) // skip ASE / Isolated
| where toint(properties.numberOfSites) > 0
| project name, resourceGroup, subscriptionId, location,
tier = tostring(sku.tier), size = tostring(sku.name),
instances = toint(sku.capacity),
apps = toint(properties.numberOfSites), id
| order by instances desc
// Step 2 (per plan, az CLI): daily CpuPercentage (average across ALL instances)
// and MemoryPercentage (PER instance) for 30 days. Both low = oversized.# Before you run this: confirm no app on the plan needs more slots,
# autoscale, per-app scaling, zone redundancy, IP SSL, or storage than the
# target tier offers, and that autoscale is not managing the instance count.
# Size to the P95 with headroom left on the target, not to the average.
# S3 x 3 -> S2 x 2 (SKU change and scale-in in one update)
az appservice plan update --name plan-web-prod --resource-group rg-prod \
--sku S2 --number-of-workers 2
# Two instances keep redundancy; one instance would not.How StratoLens helps
StratoLens flags App Service plans whose sustained load fits a smaller SKU or fewer instances, names the binding dimension and the monthly savings at your region's rates, and respects what the plan actually uses, automatically and continuously across every subscription, inside your own tenant. Plans sized for launch day do not stay that way for years.