Azure Troubleshooting

Managing Azure Quotas: vCPU Limits, Quota Groups, and Increase Requests

Forty seconds into the production rollout the pipeline turns red: QuotaExceeded — Operation could not be completed as it results in exceeding approved standardDSv5Family Cores quota. Additional details - Current Limit: 24, Current Usage: 20, Additional Required: 8 (Minimum: 28). Submit a request for Quota increase. Nothing is wrong with your Bicep. The VM size is valid, the image exists, the region is right. You have simply run into a vCPU quota — a per-subscription, per-region ceiling Azure places on how many processor cores you may allocate from a given VM size family — and the default ceiling for that family in that region was smaller than your production footprint. This is one of the most common ways an Azure deployment fails, and it bites at the worst possible moment: at scale-out, in production, under a deadline.

The maddening part is that the number you hit is rarely the number you think you hit. Azure enforces vCPU limits at three stacked layers — a Total Regional vCPUs ceiling for the whole region, a per-family vCPUs ceiling for each size family (standardDSv5Family, standardFSv2Family, …), and a separate Spot vCPUs ceiling for evictable capacity — and your deployment must fit under every layer that applies. A request can pass the family quota and still die against the regional total; it can pass both and still fail because the specific SKU isn’t offered in your zone. Knowing which ceiling (or which other limit entirely) blocked you is the whole game, because each fix is different: raise the family quota, raise the regional total, pick another family, or move the workload.

This is the troubleshooting playbook for that whole class of failure. You will read your usage-versus-limit for every vCPU dimension before it bites, localise any QuotaExceeded / SkuNotAvailable / OperationNotAllowed to the exact ceiling that stopped it with the command that confirms it and the fix, request increases the fast way (Quotas portal, az quota CLI, or as code) while knowing which auto-approve versus route to a human, and use Quota Groups to pool unused quota across subscriptions in a management group so a one-off bump doesn’t become a recurring ticket. This is the focused, vCPU-specific companion to the broader Azure Quotas and Limits: Reading Them, Hitting Them and Requesting Increases — here we go deep on compute cores and the increase machinery, not the whole limit surface.

What problem this solves

A quota is a guardrail Microsoft puts on new allocations so a single subscription can’t seize unbounded capacity in a region and starve everyone else (including you, later). Defaults are deliberately modest — often 10–20 total regional vCPUs on a fresh pay-as-you-go subscription — because Azure can’t pre-commit physical capacity to a subscription that has never used it. Sensible at the fleet level, infuriating at 40 seconds into a deploy, because the error surfaces only when you try to allocate, never before.

What breaks without this knowledge is predictable and expensive. An engineer hits QuotaExceeded, assumes the template is wrong, and burns an hour re-reading Bicep that was always correct. Or they react to SkuNotAvailable with a quota-increase ticket — which can’t help, because that error is offering, not quota — and wait days for a “not applicable” reply. Worst of all, an autoscale rule silently caps at the quota ceiling during a spike: the scale set wants 20 instances, quota allows 12, the extra eight never appear, and no alarm fires because nothing errored on the resource. Capacity you assumed was elastic turns out to have a hard, invisible lid.

Who hits this: everyone who scales compute. It bites hardest on teams promoting a small dev subscription to a larger production one (defaults don’t follow you), on AKS node pools and Scale Sets scaling past the family ceiling under load, on GPU/N-series workloads where defaults are often zero, on Spot fleets exhausting the separate spot ceiling, and on landing-zone teams standing up many subscriptions from the same low defaults. The fix is almost never “make the workload smaller” — it is “see the ceiling before you hit it, request the right one, and pool quota so you stop asking.” The rest of this article enumerates every way that block happens — three vCPU ceilings and two non-quota lookalikes — and the command that confirms each.

Learning objectives

By the end of this article you can:

Prerequisites & where this fits

You should already know the compute basics: a VM size (Standard_D4s_v5) belongs to a size family (the Dsv5 series), and the number after the family letter is roughly the vCPU count (a D4s_v5 is 4 vCPUs, a D16s_v5 is 16). You should run az in Cloud Shell, read JSON/table output, and grasp that quotas are scoped per subscription + region (the same subscription has independent ceilings in every region). Knowing what a management group is helps for Quota Groups, and familiarity with Scale Sets and AKS node pools helps because that’s where the ceiling is hit most.

This sits in the Governance & Troubleshooting track as the compute-cores deep cut of the broader Azure Quotas and Limits: Reading Them, Hitting Them and Requesting Increases (read that for the full limit surface, this for vCPU depth). It assumes the VM fundamentals from Your First Azure Virtual Machine: A Step-by-Step Deployment in Portal, CLI and PowerShell and pairs with Decoding Azure VM Series: Picking the Right D, E, F, L, N and M Family for Your Workload, because choosing the family is choosing which quota you spend. Spot quota connects to Azure Spot Virtual Machines Explained: How Eviction, Capacity and Pricing Save You up to 90%, and a request that routes to a human brings in Azure Support Plans Compared: Severity Levels, Response Times and What to Buy.

A quick map of who owns which ceiling during an incident, so you escalate to the right place fast:

Layer What it caps Who usually owns it Failure class it causes
Per-family vCPU quota Cores allocatable from one size family in a region Platform / subscription owner QuotaExceeded on a specific family
Total Regional vCPU quota Sum of all VM cores in a region Platform / subscription owner QuotaExceeded even when the family has room
Spot vCPU quota Cores allocatable as Spot in a region Platform owner Spot scale set capped / QuotaExceeded
SKU offering Whether a size exists in region/zone Microsoft (region rollout) SkuNotAvailable (no quota request helps)
Physical capacity Whether hardware is free now Microsoft (datacentre) AllocationFailed (transient, retry/relocate)
Quota Group (mgmt group) Shared quota pool across subscriptions Billing / landing-zone team Transfer fails if group quota is short

Core concepts

A few mental models make every later diagnosis obvious.

A quota is an adjustable ceiling on a counted, regional resource — not a bill and not a reservation. A vCPU quota counts how many cores you may have allocated at once from a family in a region. It’s not money (you pay for what you run) and not capacity (it doesn’t pre-book hardware); raising it costs nothing and just lifts the lid. The distinction that wastes the most hours is quota versus capacity: QuotaExceeded = your approved ceiling is too low (file a request); AllocationFailed / SkuNotAvailable = Azure can’t or won’t place that size there right now (a quota increase does nothing).

vCPU limits stack in three independent layers, and you must clear all that apply. Every VM consumes its family’s per-family quota, AND the region’s Total Regional vCPUs quota, AND — if Spot — the region’s Spot vCPUs quota. These are separate counters: a four-core D4s_v5 adds 4 to standardDSv5Family and 4 to the Total Regional counter at once. You can have ample family headroom and still be blocked by the regional total, which is why reading all three numbers — not just the one in the error — prevents the next surprise.

The error names the family by its internal quota name, not its friendly name. Azure identifies families by strings like standardDSv5Family and the regional total as cores (Total Regional vCPUs in the portal) — not the marketing names (“Dsv5-series”). Guessing the internal name is the number-one cause of a 400 Bad Request, so read it first from az vm list-usage or az vm list-skus and copy it exactly.

Two more facts shape everything downstream, both developed in their own sections below. Quota is per-subscription and per-region, and defaults don’t travel — dev’s generous eastus tells you nothing about prod’s centralindia, which is the single most common reason “it worked in dev” fails in prod (and the problem Quota Groups solve by pooling across a management group). And increase requests are partly automated — modest bumps on common families auto-approve in seconds, while large jumps, GPU SKUs and capacity-pressured regions route to a support engineer for hours to days.

The vocabulary in one table

Every moving part in one place; the glossary repeats these for lookup.

Concept One-line definition Where it lives Why it matters
vCPU / core quota Max cores allocatable at once from a scope Per subscription + region The ceiling you hit at scale-out
Total Regional vCPUs Sum-of-all-families cap for a region Per subscription + region Blocks even when a family has room
Per-family vCPUs Cap for one size family (standardDSv5Family) Per subscription + region The error you see most often
Spot vCPUs Separate cap for evictable (Spot) cores Per subscription + region Spot fleet limited independently
Quota family name Internal id like standardDSv5Family Quota API / az output Wrong name → request 400s
Usage Cores currently allocated against a limit Read via az vm list-usage The “current usage” in the error
Quota increase request An ask to raise a limit Quotas portal / az quota Auto-approves or routes to support
Quota Group Shared quota pool across subscriptions Under a management group Pool/transfer to stop re-asking
SKU availability Whether a size is offered here/in a zone az vm list-skus SkuNotAvailable ≠ quota problem
Allocation / capacity Whether hardware is free right now Datacentre, runtime AllocationFailed ≠ quota problem
Microsoft.Quota The unified quota resource provider ARM control plane Backs az quota across providers

The three vCPU ceilings, layer by layer

The single biggest source of confusion is treating “vCPU quota” as one number. It is three, and a deployment fails against the first it exceeds. The stack at a glance, including the two non-quota lookalikes that masquerade as quota errors:

Ceiling Internal name (example) Counts Typical default (new PAYG) Adjustable? Error when hit
Total Regional vCPUs cores (a.k.a. Total Regional vCPUs) Every VM core in the region, all families ~10–20 Yes QuotaExceeded (regional)
Per-family vCPUs standardDSv5Family, standardFSv2Family, … Cores from that one family in the region Low per family; 0 for some GPU Yes QuotaExceeded (family)
Spot vCPUs lowPriorityCores / spot family rows Evictable (Spot) cores in the region Low / 0 Yes QuotaExceeded (spot)
SKU availability (lookalike) n/a Whether the size is offered here/zone n/a No — not a quota SkuNotAvailable
Capacity (lookalike) n/a Whether hardware is free right now n/a No — not a quota AllocationFailed

Ceiling 1 — Total Regional vCPUs

This is the region-wide lid: the sum of every VM core running in that region across all families — your D-series web tier, E-series database tier and F-series workers all add into one counter. A fresh pay-as-you-go subscription often starts around 10–20 here, so a few medium VMs plus an AKS cluster hit it early.

Confirm it. Read the Total Regional vCPUs row:

# Total Regional vCPUs usage vs limit for one region
az vm list-usage --location centralindia \
  --query "[?contains(localName,'Total Regional vCPUs')].{name:localName, used:currentValue, limit:limit}" \
  -o table

If used is at or near limit, the regional total is your blocker regardless of family — the tell-tale is an error whose family line shows headroom yet the deploy still fails, because the regional total (a different counter) is full.

Fix it. Request a higher Total Regional limit, or move footprint to another region so the sum drops. Raising the regional total does not raise any per-family ceiling — they’re independent, so a large regional total with a small family quota still blocks that family.

Ceiling 2 — Per-family vCPUs

The one you see in error messages most. Each VM size family has its own regional ceiling, named with an internal string (standardDSv5Family, …; mapped below). A Standard_D4s_v5 consumes 4 cores from standardDSv5Family; a Standard_D16s_v5 consumes 16. The default is low, and for many N-series GPU sizes it’s literally 0 — you cannot allocate even one until you request quota.

Confirm it. First find which family your SKU maps to (don’t guess):

# Map a VM size to its quota family name and core count
az vm list-skus --location centralindia --size Standard_D4s_v5 \
  --query "[0].{size:name, family:family, vCPUs:capabilities[?name=='vCPUs'].value | [0]}" -o json
# -> "family": "standardDSv5Family"

Then read that family’s usage vs limit:

# Per-family usage vs limit (substitute the family from the step above)
az vm list-usage --location centralindia \
  --query "[?name.value=='standardDSv5Family'].{family:localName, used:currentValue, limit:limit}" \
  -o table

The error itself states the family, current limit, usage and additional required — e.g. “exceeding approved standardDSv5Family Cores quota. Current Limit: 24, Current Usage: 20, Additional Required: 8” — so it already names the family and how many more cores you need.

Fix it. Request a higher per-family ceiling (cleanest), or switch to a family that has headroom — the fastest mitigation in an incident: if Dsv5 is capped but Dsv4/Edsv5 fits, retargeting the SKU clears the block immediately without approval, at the cost of a slightly different price/performance point.

Because the internal name is what you pass to a request — and the wrong one is the usual 400 — keep this size-to-family map handy (always confirm with az vm list-skus --size <size> --query "[0].family"):

Common size (example) Series Internal quota family name Typical use
Standard_D4s_v5 Dsv5 standardDSv5Family General-purpose web/app tier
Standard_E8ds_v5 Edsv5 standardEDSv5Family Memory-optimised (DB, cache)
Standard_F8s_v2 Fsv2 standardFSv2Family Compute-optimised (batch, CI)
Standard_B2s Bsv… standardBSFamily Burstable dev/test
Standard_L8s_v3 Lsv3 standardLSv3Family Storage-optimised (high IOPS)
Standard_NC24ads_A100_v4 NC A100 v4 standardNCADSA100v4Family GPU compute (often defaults to 0)
any regular VM cores (Total Regional vCPUs) The region-wide sum across families

Ceiling 3 — Spot vCPUs

Spot VMs (evictable, deeply discounted capacity) draw from a separate vCPU ceiling — Spot cores don’t consume your standard quota and aren’t constrained by it; they’re gated by the Spot ceiling, which often starts low or at zero. This catches teams who raised their regular quota generously and then watched a Spot scale set refuse to grow — because the Spot counter, not the regular one, was full.

Confirm it. The spot rows appear in the same usage list, named for low-priority/spot cores:

# Spot / low-priority vCPU usage vs limit
az vm list-usage --location centralindia \
  --query "[?contains(localName,'Spot') || contains(localName,'Low')].{name:localName, used:currentValue, limit:limit}" \
  -o table

Fix it. Request a higher Spot vCPU quota the same way (just a different quota name). Remember Spot is also subject to eviction and capacity independently of quota — even with ample quota, Azure can evict or refuse instances when it needs the hardware back.

How the three interact — who blocks first

A deployment is checked against every applicable ceiling and fails against the first it would exceed — reasoning about that order prevents the “I raised the quota and it still failed” loop.

Scenario Family quota Regional total Spot quota What happens
Regular VM, family full Exceeded Has room n/a Fails: family QuotaExceeded
Regular VM, region total full Has room Exceeded n/a Fails: regional QuotaExceeded (raise the total)
Regular VM, both have room Has room Has room n/a Succeeds (subject to SKU/capacity)
Spot VM, spot full, regular fine Has room Has room Exceeded Fails: spot QuotaExceeded
GPU SKU not offered here, quotas fine Has room Has room n/a Fails: SkuNotAvailable (wrong region/zone)
Constrained size, all quotas fine Has room Has room n/a May fail: AllocationFailed (capacity)

The discipline that falls out: read all three numbers and verify the SKU is offered before scale-out, so you fix the ceiling that actually blocks you.

Reading usage before it bites

You never want to discover a ceiling from a failed deploy. Two surfaces give you the numbers ahead of time — the compute-specific az vm list-usage / az network list-usages, and the unified az quota (read and write across providers via Microsoft.Quota).

The compute-specific usage commands

az vm list-usage is the fastest read for vCPU ceilings — every compute row for a region with currentValue (used) and limit:

# Every compute quota row for a region, sorted to surface the tightest first
az vm list-usage --location centralindia -o table

# Just the rows that are >80% consumed — your early-warning list
az vm list-usage --location centralindia \
  --query "[?limit > \`0\` && currentValue >= (limit * \`0.8\`)].{name:localName, used:currentValue, limit:limit}" \
  -o table

For network ceilings (public IPs, NICs), the sibling is az network list-usages --location <region>.

The unified az quota surface

The newer az quota group reads and writes quotas across providers via Microsoft.Quota. For compute, build the scope from subscription and location, then list, show and check usage:

SUB=$(az account show --query id -o tsv)
SCOPE="/subscriptions/$SUB/providers/Microsoft.Compute/locations/centralindia"

# All compute quotas (limits) at that scope
az quota list --scope "$SCOPE" -o table

# Current usage at that scope
az quota usage list --scope "$SCOPE" -o table

# One specific quota — the regional total is named 'cores'
az quota show --resource-name cores --scope "$SCOPE" -o json
Command Reads Writes Best for
az vm list-usage Compute usage + limit per region No Fast vCPU read, early-warning scan
az network list-usages Network usage + limit per region No Public IPs, NICs alongside a VM deploy
az quota list / usage list / show Any provider via Microsoft.Quota Cross-provider reads, scripting
az quota create / update Yes (submits a request) Programmatic increase requests
az quota request status list / show Request state No Tracking whether a bump approved

The naming rule that saves a 400: the regional total’s --resource-name is cores and a family’s is its internal string — read the exact value from az quota list --scope "$SCOPE" before passing it to create/update.

Reading pays off on a cadence — wire these so a ceiling is verified, not discovered:

When Check Action if tight
Before any scale-out / new SKU az vm list-usage for the target family + Total Regional Raise the binding ceiling first
Daily (scheduled) Usage ≥ 80% of limit for key families/regions Alert/page; request ahead of need
New subscription or region Compare defaults to your known footprint Bootstrap quota; mirror prod into DR
Before a failover test DR-region family + regional limits Match them to production
Onboarding a GPU workload N-family limit (often 0) Request early — routes to support

Requesting a vCPU increase

Once you know which ceiling blocks you and by how much, request the increase. Three paths, by whether you’re firefighting once or codifying a baseline.

Path Best for Auto-approve likely? Audit trail
A — Quotas portal One-off, interactive, “just unblock me now” Yes for modest common-family bumps Activity log
B — az quota CLI Scripts, runbooks, repeatable bumps Same engine as portal Command + request ID
C — IaC / pipeline Codifying baseline quotas for new subscriptions Same engine Source control + pipeline log

Path A — the Quotas portal

The Quotas experience (portal.azure.comQuotasCompute) lists each region’s usage as a bar and offers an Increase action on every adjustable row. Type the new absolute limit (not the delta — have 24, need 28, enter 28); for a modest common-family bump it frequently flips to Approved within seconds, otherwise it routes to support with a trackable request. The right path mid-incident.

Path B — the az quota CLI

For automation or speed, az quota creates or updates a request at the provider+region scope, setting the new absolute limit:

SUB=$(az account show --query id -o tsv)
SCOPE="/subscriptions/$SUB/providers/Microsoft.Compute/locations/centralindia"

# Raise the per-family ceiling to a new absolute value (e.g. 28 cores)
az quota create \
  --resource-name standardDSv5Family \
  --scope "$SCOPE" \
  --limit-object value=28 \
  --resource-type dedicated

# If a quota object already exists for that resource, use update instead
az quota update \
  --resource-name standardDSv5Family \
  --scope "$SCOPE" \
  --limit-object value=28

# Track it
az quota request status list --scope "$SCOPE" -o table
az quota request status show --name <requestId> --scope "$SCOPE" -o json

To raise the regional total instead, use --resource-name cores. The exact inputs and the trap for each:

Input What to pass Common mistake
--resource-name Internal name: standardDSv5Family (family) or cores (regional total) Friendly name (“Dsv5”) or a typo → 400
--scope /subscriptions/<sub>/providers/Microsoft.Compute/locations/<region> Wrong subscription or region in the path
--limit-object value= The new absolute limit (have 24, need 28 → 28) Passing the delta (4) instead of the target
--resource-type dedicated (regular) — Spot rows use their own name Mixing Spot and regular in one request
Region The region you actually deploy to Raising the right family in the wrong region

Path C — codify it as a pipeline/IaC step

Quota requests are an imperative ask against Microsoft.Quota, not a declarable Bicep resource. The clean landing-zone pattern wraps the az quota call in a Bicep deploymentScript so the request lives in source control and runs at subscription bootstrap with a full audit trail:

param location string
param targetFamily string = 'standardDSv5Family'
param newLimit int = 28

resource raiseQuota 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
  name: 'raise-${targetFamily}-${location}'
  location: location
  kind: 'AzureCLI'
  properties: {
    azCliVersion: '2.61.0'
    retentionInterval: 'PT1H'
    scriptContent: '''
      SCOPE="/subscriptions/${SUB}/providers/Microsoft.Compute/locations/${LOC}"
      az quota create --resource-name "$FAM" --scope "$SCOPE" \
        --limit-object value=$LIMIT --resource-type dedicated
    '''
    environmentVariables: [
      { name: 'SUB', value: subscription().subscriptionId }
      { name: 'LOC', value: location }
      { name: 'FAM', value: targetFamily }
      { name: 'LIMIT', value: string(newLimit) }
    ]
  }
}

The script is the unit of change — versioned and reviewed. For a cleaner multi-subscription baseline, Quota Groups (next) let you allocate from a shared pool instead of filing the same request per subscription.

Who auto-approves and who waits

Predicting the path lets you plan the window:

Request shape Likely route Typical time Why
Modest bump, common family, healthy region Auto-approved Seconds System has headroom and confidence
Large jump on a common family Support review Hours Bigger commitment, sanity-checked
Any N-series / GPU family from 0 Support review Hours–days Specialised, capacity-constrained
Region under capacity pressure Support review (may decline) Hours–days Azure may have no room to grant
Spot vCPU bump Often auto / quick Seconds–hours Evictable, less commitment
Total Regional vCPUs raise Usually auto for modest Seconds Same engine as family

If a request routes to support and you’re blocked, the severity and response time you get depends on your support plan — see Azure Support Plans Compared: Severity Levels, Response Times and What to Buy. Quota requests through the Quotas experience do not require a paid support plan, but escalating a stuck one does.

Quota Groups — pooling quota across subscriptions

The painful pattern in any multi-subscription estate: each subscription starts from the same low defaults, so you file the same per-family ticket over and over, and quota stranded in one subscription can’t help another that’s short. Quota Groups fix this. A Quota Group is a quota pool on a management group — think shared wallet. The platform team obtains quota at the group level once; then any member subscription allocates from the pool into itself for a region/family and deallocates (returns) what it isn’t using so a sibling can take it — turning “10 subscriptions × 1 ticket each” into “1 group grant + self-service.”

Operation What it does When you use it Net effect
Create group quota Establish/raise the shared pool for a family+region Standing up the management group’s capacity One ticket feeds many subs
Allocate to subscription Move quota from the pool into a member sub A sub needs more cores now Sub’s limit rises, no per-sub ticket
Deallocate from subscription Return unused quota to the pool A sub scaled down / project ended Frees quota for a sibling sub
Transfer between subscriptions Shift quota from sub A to sub B via the pool Rebalancing without new grants Stranded quota becomes usable

Key constraints: Quota Groups operate within one management group whose subscriptions are members; allocations are still per region and per family; and the initial group quota goes through the same request engine — they change distribution, not whether Azure has capacity to grant. The win is operational: after the one-time grant, members self-serve without a support loop. Quota Groups assume the management-group hierarchy exists and are the capacity-distribution layer on top.

Architecture at a glance

No diagram to study here — just one mental model that locates every error in the playbook. Picture a deployment request travelling down a stack of gates, each a counter it must fit under, and the first gate it overflows is the one that rejects it.

An allocation enters carrying a size and count that resolve to a vCPU demand and a family. Gate 1 is the per-family vCPU quota (usage + demand > family limit → family QuotaExceeded). Gate 2 is the Total Regional vCPUs quota, which the same demand is added to — a larger-scope counter that can reject a request the family gate just passed. Spot requests face a parallel Spot vCPU gate. Below the quota gates sit two checks no increase can move: SKU availability (offered in this region/zone? → SkuNotAvailable) and physical capacity (free hardware now? → AllocationFailed). Above everything, the Quota Group feeds the per-subscription limits, so a gate’s “limit” can be topped up from the shared pool without a ticket.

So the diagnostic question is always which gate did I overflow? — a quota gate (raise it or pick another family) or a non-quota gate below it (availability → change region/zone; capacity → retry or relocate). Reading all three counters and confirming availability before scale-out is just walking this gate-stack on paper before the deployment walks it for real.

Real-world scenario

Lumio Retail, a mid-size e-commerce company, runs its catalogue and checkout APIs on an AKS cluster in centralindia, with a Standard_D4s_v5 user node pool (4 vCPUs/node) autoscaling between 4 and 12 nodes. It had run cleanly for months — then came their first big festival sale.

At 19:40 on sale day, traffic spiked and the cluster autoscaler asked to grow the pool from 9 to its max of 12. Three nodes never appeared. No error on the cluster, no failed deployment, no alert — the autoscaler simply logged that it couldn’t scale, and checkout latency climbed as the existing nodes saturated. The on-call engineer assumed an AKS problem and spent twenty minutes in the cluster’s events before someone ran the one command that mattered:

az vm list-usage --location centralindia \
  --query "[?name.value=='standardDSv5Family'].{family:localName, used:currentValue, limit:limit}" -o table
# family: Standard DSv5 Family vCPUs | used: 48 | limit: 48

The standardDSv5Family quota in centralindia was 48 cores — exactly 12 nodes × 4 vCPUs. The system and user pools together had consumed all 48, so nodes 10–12 (the ones the sale needed) had nowhere to allocate. A default set long ago and never revisited had silently become the capacity ceiling; nothing “failed” in the way an alert watches for — the allocation just didn’t happen.

The fix was a quota bump: they raised standardDSv5Family from 48 to 96 via the Quotas portal, and because it was a modest increase on a common family in a healthy region it auto-approved in under a minute. The autoscaler kept retrying, picked up the headroom, and the three nodes joined within a couple of minutes — checkout recovered before the sale peaked.

The post-incident review surfaced three lessons. First, autoscale silently caps at the quota ceiling — invisible to “something errored” alerting — so they added a scheduled check that pages when usage crosses 80% of limit for key families. Second, the quota had to be raised in every region they run in: their DR region had the same 48-core default and would have failed an actual failover. Third, they moved the AKS subscriptions under a management group with a Quota Group, granted the family pool once at the group level, and let each subscription allocate from it — so the next environment inherited capacity instead of a ticket. The whole incident was a number someone could have read the week before.

Advantages and disadvantages

Quotas are a guardrail with real upside and real friction:

Advantages Disadvantages
Prevents a runaway script/subscription from seizing a region’s capacity Blocks legitimate scale-out at the worst moment (peak load)
Forces deliberate capacity planning per region Defaults are low and don’t follow you to new subs/regions
Increase requests are free and often auto-approve in seconds Larger / GPU / constrained asks route to support and wait
Three-layer model lets Azure fairly share scarce families Three layers are easy to confuse → wrong fix, wasted time
Quota Groups let an estate pool and rebalance capacity Quota Groups need a management group and add a concept to learn
Reading usage is a one-line az command Autoscale capping at quota is silent — no error to alert on
A quota grant costs nothing until you actually run the VMs A grant is not a capacity reservation — hardware can still be unavailable

When the upside dominates: in any shared or production estate, the guardrail is worth it — you want a deliberate ceiling, and reading usage before scale-out turns the worst-moment block into a non-event. The friction bites hardest on GPU workloads from a zero default and multi-subscription estates re-filing the same ticket — exactly where requesting ahead of need and adopting Quota Groups pays off.

Hands-on lab

Read your real vCPU ceilings, watch an allocation consume quota, and (optionally) request a small increase — all free; we delete the one tiny VM at the end. Run in Cloud Shell (Bash).

Step 1 — Read your Total Regional vCPUs.

LOC=centralindia
az vm list-usage --location $LOC \
  --query "[?contains(localName,'Total Regional vCPUs')].{name:localName, used:currentValue, limit:limit}" \
  -o table

Expected: one row with your regional total used vs limit.

Step 2 — List your tightest quotas (early-warning scan).

az vm list-usage --location $LOC \
  --query "[?limit > \`0\` && currentValue >= (limit * \`0.5\`)].{name:localName, used:currentValue, limit:limit}" \
  -o table

Expected: every quota you’ve used at least half of (empty on a quiet subscription is fine).

Step 3 — Map a size to its family and confirm headroom.

az vm list-skus --location $LOC --size Standard_B1s \
  --query "[0].{size:name, family:family}" -o json

az vm list-usage --location $LOC \
  --query "[?contains(localName,'BS') || name.value=='standardBSFamily'].{family:localName, used:currentValue, limit:limit}" \
  -o table

Expected: the size’s family string, then that family’s used/limit — the exact pair of reads you’d do mid-incident to find the blocking ceiling.

Step 4 — Allocate one tiny VM and watch the counter rise.

RG=rg-quota-lab
az group create -n $RG -l $LOC -o table
az vm create -n vm-quota-lab -g $RG --image Ubuntu2204 \
  --size Standard_B1s --admin-username azureuser --generate-ssh-keys \
  --public-ip-sku Standard --no-wait -o table

Wait ~60 s, then re-read the family from Step 3 — used rose by the VM’s vCPU count (a B1s is 1 vCPU). You just watched usage tick up against the ceiling.

Step 5 — Confirm az quota agrees.

SUB=$(az account show --query id -o tsv)
SCOPE="/subscriptions/$SUB/providers/Microsoft.Compute/locations/$LOC"
az quota usage list --scope "$SCOPE" -o table | head -20
az quota show --resource-name cores --scope "$SCOPE" -o json

Expected: the same numbers via the cross-provider API; the regional total appears as cores.

Step 6 (optional) — Submit a tiny increase request. Only if you want more headroom; this is a real request. Raise the regional total to a new absolute value:

# Read the current limit first, then set new = current + a little
az quota show --resource-name cores --scope "$SCOPE" --query "properties.limit.value" -o tsv
az quota create --resource-name cores --scope "$SCOPE" --limit-object value=<current+4> --resource-type dedicated
az quota request status list --scope "$SCOPE" -o table

Expected: a request that, for a small bump, typically shows Succeeded/Approved quickly.

Validation checklist. You read the vCPU ceilings, mapped a SKU to its family, watched an allocation consume quota, cross-checked via az quota, and (optionally) filed an increase. The steps mapped to what they prove:

Step What you did What it proves
1–2 Read regional total + early-warning scan You can see ceilings before they bite
3 Map size → family, read family limit You find the blocking ceiling, not a guess
4 Create a VM, watch usage rise Allocation consumes quota in real time
5 Cross-check via az quota The unified surface agrees; naming is cores
6 File a small increase The request path and auto-approval are real

Cleanup.

az group delete -n $RG --yes --no-wait

Cost note. A B1s is a few paise per hour and the rest is read-only API calls; an hour of this lab is well under ₹20, and deleting the resource group stops everything. Reading quotas and filing increases is always free.

Common mistakes & troubleshooting

This is the playbook — the part you bookmark. First the error-code reference (the exact strings, what each means, and whether a quota increase even applies), then a scannable symptom→cause→confirm→fix table for mid-incident, then full reasoning for the entries that bite hardest. The costliest codes are the non-quota lookalikesSkuNotAvailable and AllocationFailed — which read like quota problems but no increase fixes.

Error code / string What it means on Azure Quota-fixable? How to confirm First fix
QuotaExceeded (family) Per-family vCPU ceiling reached Yes az vm list-usage family row at limit; error states Current Limit/Usage Raise that family or retarget to one with headroom
QuotaExceeded (Total Regional vCPUs) Region-wide cores ceiling reached Yes az vm list-usageTotal Regional vCPUs row at limit Raise --resource-name cores, or shed region footprint
OperationNotAllowed (cores) Request would exceed an allowed core count Yes Error text names the limit and required cores Raise the named ceiling; lower count × size
SkuNotAvailable Size not offered in this region/zone No — offering, not quota az vm list-skus -l <region> --size <size> --zone <z> empty/restricted Change region/zone or size
AllocationFailed No free hardware for size+zone now No — capacity, not quota Error is AllocationFailed, quotas show headroom Retry, relax zone/size, or Capacity Reservation
OverconstrainedAllocationRequest Too many constraints (zone+size+pinning) can’t be placed No — capacity Same as above with multiple constraints Relax a constraint (zone/size)
400 Bad Request (on az quota create) Wrong/typo’d --resource-name n/a (request error) Compare to az quota list --scope "$SCOPE" Pass the exact internal name
RequestDisallowedByPolicy Azure Policy blocked the size/region No — governance Activity log shows the policy assignment Use an allowed size/region or amend policy

With the codes pinned, the symptom-first playbook:

# Symptom Root cause Confirm (exact cmd / portal path) Fix
1 Deploy dies QuotaExceeded naming a family Per-family vCPU ceiling reached az vm list-usage -l <region> --query "[?name.value=='<family>']" (used≈limit) Raise that family, or retarget to one with headroom
2 Family has room, deploy still QuotaExceeded Total Regional vCPUs ceiling reached (different counter) az vm list-usageTotal Regional vCPUs row Raise the regional total (--resource-name cores), or move footprint
3 Autoscale/AKS won’t grow, no error, no alert Allocation silently capped at quota ceiling az vm list-usage family row at limit; AKS event “could not scale” Raise the family quota; add an 80%-usage alert
4 az quota create returns 400 Bad Request Wrong --resource-name (friendly name / typo) az quota list --scope "$SCOPE" for the exact internal name Pass the exact string (standardDSv5Family, cores)
5 GPU/N-series VM fails with quota 0 Default per-family quota is 0 for that family az vm list-usage shows limit 0 for the N-family Request the GPU family quota (routes to support; hours–days)
6 SkuNotAvailable; request “not applicable” Size isn’t offered in that region/zone (not quota) az vm list-skus -l <region> --size <size> --zone <z> (empty) Change region/zone or pick an offered size — no increase helps
7 Quota fine, deploy fails AllocationFailed Capacity — no hardware for that size+zone now Error is AllocationFailed/OverconstrainedAllocationRequest Retry, relax zone/size, or use a Capacity Reservation
8 Spot scale set won’t grow though regular quota is huge Spot vCPU ceiling is separate and full/zero az vm list-usage Spot/Low rows Raise the Spot quota specifically
9 “It worked in dev, fails in prod” New subscription/region inherits low defaults Compare az vm list-usage between the two Raise quota in the prod sub/region; consider Quota Groups
10 Increase request stuck “pending”/in review Large/constrained ask routed to a human az quota request status show --name <id> --scope "$SCOPE" Wait/escalate via support; meanwhile retarget family/region
11 Raised quota, deploy still fails same family Raised the wrong region, or the regional total not the family (or vice versa) Re-read the family and Total Regional rows for the exact region deployed Raise the ceiling that’s actually at limit, in the deploy region
12 Reservation/scale-set reports quota error at create Total count × size exceeds family or regional cap Sum instances × vCPUs vs the two limits Lower count, split across regions, or raise the binding ceiling first
13 Quota Group transfer/allocation fails Group pool short, or sub not in the management group Check MG membership and group quota balance Top up the pool; fix membership; allocate per region+family
14 Portal shows quota OK but CLI deploy fails Wrong region or subscription context az account show; confirm --location matches the deploy Align subscription (az account set) and region

The entries whose table row alone doesn’t capture why:

The family has room, but the deploy still returns QuotaExceeded (row 2). You hit the Total Regional vCPUs ceiling — a separate, region-wide counter summing all families. Confirm with az vm list-usage -l <region> where the Total Regional vCPUs row is at limit even though the family row isn’t. Fix by raising the regional total (az quota create --resource-name cores ...), not the family — raising the family does nothing here — or shed footprint to another region.

Autoscale or AKS won’t add nodes, with no error and no alert (row 3). The autoscaler asked for capacity exceeding a vCPU ceiling, so the allocation silently didn’t happen — it’s not an error on the resource. Confirm with az vm list-usage showing the family or regional row at its limit while the instance count stays flat under load. Fix by raising the binding quota and, critically, adding an alert comparing currentValue to limit at ~80% so it never again hides behind silence. This is the costliest failure mode because nothing pages you.

The two non-quota lookalikes — SkuNotAvailable and AllocationFailed (rows 6–7). These read like quota errors but no increase fixes either, which is why they waste the most time. SkuNotAvailable means the size isn’t offered in that region/zone (an availability fact — confirm with az vm list-skus ... --zone <z> returning empty; fix by choosing an offered region/zone/size). AllocationFailed / OverconstrainedAllocationRequest means physical capacity — no free hardware for that size+zone right now; quota is your permission, capacity is the hardware. Fix by retrying, relaxing the zone/size constraint, or guaranteeing placement with a Capacity Reservation.

Best practices

Security notes

Quotas are a capacity guardrail, but the operations around them have a security surface worth locking down:

Cost & sizing

The most important cost fact is the one people misunderstand: a quota costs nothing. Raising a vCPU limit adds nothing to your bill — you pay only for VMs you run, so there’s no cost reason to keep quotas low (only the blast-radius reasons above). What drives cost is what you run under the ceiling:

A rough picture for a small production AKS footprint: a Standard_D4s_v5 pool autoscaling 4–12 nodes uses 16–48 vCPUs and runs roughly ₹70,000–₹2,10,000/month depending on time at peak (pay-as-you-go Linux, indicative — price your exact region/SKU). The quota to support it (≥48 family cores plus regional headroom) is free; only the running nodes cost. Move the burst to Spot and peak cost can halve, gated by the separate, also-free Spot quota.

Interview & exam questions

1. What are the three vCPU ceilings a VM deployment must clear, and how do they relate? Per-family vCPUs (cores from one size family in a region), Total Regional vCPUs (sum of all families), and — for Spot VMs — Spot vCPUs (a separate evictable ceiling). A regular VM must fit under both the family and the regional total; a Spot VM under the spot ceiling too. The deploy fails against whichever it overflows first, so a request can pass the family quota and still fail the regional total.

2. A deployment fails QuotaExceeded on a family, but az vm list-usage shows that family has headroom. What’s happening? It hit the Total Regional vCPUs ceiling, a different counter that sums every family in the region. The family gate passed; the regional gate didn’t. The fix is to raise the regional total (--resource-name cores) or shed footprint to another region — raising the family quota would do nothing.

3. How do you tell a quota problem from a SKU-availability problem from a capacity problem? QuotaExceeded = your approved ceiling is too low (adjustable; file a request). SkuNotAvailable = the size isn’t offered in that region/zone (change region/zone/size; a quota request can’t help). AllocationFailed = no free hardware for that size+zone right now (retry, relocate, or use a Capacity Reservation). Only the first is solved by an increase.

4. Why does “it worked in dev, fails in prod” happen with quotas? Quotas are per-subscription and per-region, and defaults don’t travel. The production subscription (or a new region) starts from Azure’s low defaults that the dev subscription outgrew long ago. You must raise quota in the actual sub/region you deploy to; Quota Groups can let an estate inherit pooled capacity instead.

5. An AKS cluster won’t add nodes during a spike, with no error and no alert. What is the likely cause and the safeguard? The cluster autoscaler asked for capacity exceeding a vCPU quota, so the allocation silently didn’t happen — it’s not an error on the resource. Confirm with az vm list-usage showing the family/regional row pinned at its limit. The safeguard is an alert comparing currentValue to limit at ~80%, because this failure is invisible to ordinary error alerting.

6. What is a Quota Group and what problem does it solve? A Quota Group is a shared quota pool associated with a management group; member subscriptions allocate quota from the pool into themselves and return (deallocate) unused quota for siblings to use. It replaces filing the same per-family increase ticket across many subscriptions with a one-time group grant plus self-service allocation, and lets stranded quota in one subscription be rebalanced to another.

7. You run az quota create for a vCPU bump and get 400 Bad Request. Most likely cause? The --resource-name is wrong — you passed the friendly name or a typo instead of the internal string. Use the exact value from az quota list --scope "$SCOPE": standardDSv5Family for a family, cores for the Total Regional vCPUs. Guessing the name is the usual cause of the 400.

8. Which vCPU increase requests auto-approve and which route to a human? Modest bumps on common families in healthy regions typically auto-approve in seconds. Large jumps, specialised/GPU families (often defaulting to 0), and capacity-pressured regions route to a support engineer and take hours to days. Spot and Total-Regional bumps for modest amounts usually auto-approve. Knowing the path lets you plan the deploy window.

9. Does raising a quota guarantee you can deploy that many cores? Does it cost anything? No to both as people assume: a quota is permission, not a hardware reservation — you can still hit AllocationFailed if capacity is short — and raising it is free (you pay only for VMs you run). For guaranteed placement of a constrained size you need a Capacity Reservation, which charges whether or not you use it.

10. A Spot scale set won’t grow even though your regular quota is large. Why? Spot VMs draw from a separate Spot vCPU ceiling, not your regular regional/family quotas, and the Spot one is often low or zero. Raise the Spot quota specifically. Note that even with ample Spot quota, instances can be evicted or refused on capacity grounds independent of quota.

11. How would you proactively prevent quota surprises across a 20-subscription estate? Codify baseline quotas in subscription bootstrap (config-driven az quota calls), mirror production quotas into DR regions, alert on usage at ~80% per key family/region, and adopt Quota Groups so subscriptions allocate from a shared pool rather than each filing tickets — making QuotaExceeded a number you verified last week, not a 2 a.m. discovery.

12. Where does az quota get its cross-provider ability, and what’s the scope you pass for compute? It works through the unified Microsoft.Quota resource provider, which fronts per-provider quotas. For compute you pass a provider+region scope: /subscriptions/<sub>/providers/Microsoft.Compute/locations/<region>, then list/show/create against resource names like cores or a family string.

These map to AZ-104 (Administrator) — manage subscriptions, governance and compute — and AZ-305 (Solutions Architect) — design for capacity and scale, where vCPU limits and Quota Groups inform subscription/region topology. A compact cert-mapping for revision:

Question theme Primary cert Objective area
Three vCPU ceilings; reading usage AZ-104 Manage subscriptions, governance, compute
Increase requests; az quota AZ-104 Configure resource policies; CLI
Quota Groups; multi-sub capacity AZ-305 Design governance & scale
Quota vs capacity vs availability AZ-305 / AZ-104 Design/operate compute reliably
Autoscale capped at quota AZ-104 Configure and monitor scale

Quick check

  1. A deployment fails QuotaExceeded naming standardDSv5Family, but az vm list-usage shows that family at 40/96 cores. What other ceiling do you check, and what command?
  2. True or false: requesting and being granted a vCPU quota increase guarantees you can deploy that many cores immediately.
  3. Your az quota create for a family bump returns 400 Bad Request. Name the single most likely cause and how to get the right value.
  4. An autoscaling AKS pool stops adding nodes at peak with no error in the cluster and no alert. What is the most likely cause, and the one safeguard that would have caught it?
  5. You raised the standardDSv5Family quota in eastus but the prod deploy in centralindia still fails the same way. Why?

Answers

  1. Check the Total Regional vCPUs ceiling — a separate region-wide counter — with az vm list-usage -l <region> --query "[?contains(localName,'Total Regional vCPUs')]". The family gate passed; the regional total is likely the blocker, and you’d raise --resource-name cores, not the family.
  2. False. A quota is permission, not a hardware reservation. You can still hit AllocationFailed if Azure lacks free capacity for that size/zone. For guaranteed placement you need a Capacity Reservation.
  3. The --resource-name is wrong — likely the friendly name or a typo instead of the internal string. Read the exact value from az quota list --scope "$SCOPE" (e.g. standardDSv5Family, cores) and pass it verbatim.
  4. The autoscaler asked for capacity exceeding a vCPU quota, so the allocation silently didn’t happen — it’s not an error on the resource. Confirm with az vm list-usage showing the family/regional row at its limit. The safeguard is an alert comparing currentValue to limit at ~80%, since this failure is invisible to ordinary error alerting.
  5. Quotas are per-region and defaults don’t travel — eastus and centralindia have independent ceilings. You raised the wrong region; raise the family (and regional total) quota in centralindia, the region you actually deploy to.

Glossary

Next steps

You can now localise any vCPU allocation failure to the exact ceiling, request increases the fast way, and pool quota across an estate. Build outward:

AzureQuotasvCPUQuota GroupsCapacity PlanningAzure CLIComputeAZ-104
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading