Azure Governance

Azure Quotas and Limits: Reading Them, Hitting Them and Requesting Increases

Your Bicep deployment has run clean in dev for a week. You promote it to production, the pipeline kicks off, and forty seconds in it dies with QuotaExceeded: “Operation could not be completed as it results in exceeding approved standardDSv5Family Cores quota. Additional details - Current Limit: 10, Current Usage: 8, Additional Required: 8.” Nothing is wrong with your template. You have simply hit a quota — a per-subscription, per-region ceiling Azure puts on how much of a given resource you may create — and the default ceiling for that VM family in that region was lower than your production footprint. The fix is not a code change; it is a quota increase request, and if you know the path it takes ninety seconds and self-approves. If you don’t, you open a support ticket, wait, and ship late.

This article maps that territory. Azure lumps two different things under “limits,” and confusing them wastes hours. A quota (also called a service limit or usage limit) is an adjustable ceiling on a counted resource — vCPUs in a family, public IPs, storage accounts per region, network interfaces. You see your current usage against it and, for most, raise it. A hard limit is a fixed architectural boundary — max data disks on a VM size, the 65,536 addresses in a /16 VNet, subscriptions per tenant — that no asking moves; you design around it. Which kind you face is the entire difference between “file a request” and “re-architect.”

By the end you’ll read the usage-vs-limit numbers for any resource before they bite, in the portal and from az; tell an adjustable quota from a fixed limit on sight; request an increase three ways (the Azure Quotas experience, the az quota CLI, and as IaC) and know which requests auto-approve versus route to a human; and build a capacity-planning habit so the QuotaExceeded at 40 seconds into a production deploy becomes a number you checked last week, not a 2 a.m. surprise.

What problem this solves

Quotas exist for good reasons invisible until they cost you: Azure pre-allocates capacity per region and protects shared pools from a single subscription (or a runaway script, or a compromised credential) consuming everything. A fresh Pay-As-You-Go subscription doesn’t get unlimited cores on day one — often just ~10 total regional vCPUs plus small per-family allowances, raised as your footprint grows. The pain is purely informational: the limit is real, the usage is real, the gap is perfectly visible — but only if you look before the deployment, not after it fails.

What breaks without this knowledge is predictable and expensive. A team scales out for a launch and autoscale silently caps because the regional vCPU quota is exhausted — the site degrades not from a bug but because Azure refused instance number eleven. A DR failover fails because nobody ever requested quota in the secondary region. A Terraform apply of fifty resources rolls back halfway after tripping the public-IP quota on resource thirty-one. Each is a quota you could have read in advance.

Who hits this hardest: the people moving fast. New subscriptions hit it first (low defaults). Bursty workloads (batch, CI agents, flash-sale scale-out) hit vCPU and Spot quotas. Multi-region DR designs hit per-region quotas in the failover region. AKS node pools, VMSS and Azure Batch multiply vCPU consumption fast. And any CI/CD pipeline that creates real infrastructure hits it as a deployment failure — the worst time to discover a quota. The cure: treat quota like cost — monitor it and request ahead of need.

The spectrum runs from adjustable quotas (vCPUs per family, public IPs — raise on request, mostly automatic) through soft per-region caps (storage accounts, 250→500 via support) to fixed limits no request moves: hard platform (max data disks on a SKU → bigger SKU), hard architectural (65,536 IPs in a /16 → more VNets/peering), subscription-structural (~980 RGs → new subscription) and tenant-structural (subscriptions per EA → your account team). The next section sorts these into two buckets you can identify on sight.

Learning objectives

By the end of this article you can:

Prerequisites & where this fits

You should already understand the Azure resource hierarchy: a management group contains subscriptions, a subscription contains resource groups, and resource groups contain resources. Quotas live almost entirely at the subscription + region level, so knowing what a subscription is — a billing and scope boundary — is the foundation. The Azure Resource Hierarchy Explained: Subscriptions, Resource Groups and Resources and Azure Subscriptions Explained: Types, Billing Boundaries and When to Create a New One give you that base.

You should be comfortable running az in Cloud Shell or a local terminal, reading JSON, and have at least Contributor (ideally a role with Microsoft.Support/* and Microsoft.Quota/*) on the subscription. You don’t need to know how to deploy the resources whose quota you’re reading — listing usage is read-only.

This sits in the Governance & Capacity Planning track, upstream of almost everything operational. It pairs with cost work — the Azure Cost Management for Beginners: Budgets, Alerts and Cost Analysis in Your First 30 Days habit is the same muscle pointed at money — and with Azure Support Plans Compared: Severity Levels, Response Times and What to Buy, since the human path for a large increase is a support request. Choosing VM sizes bumps straight into both quotas (vCPUs) and hard limits (disks per SKU), so Decoding Azure VM Series: Picking the Right D, E, F, L, N and M Family for Your Workload is a close neighbour.

Which layer owns which kind of limit, so you escalate right:

Scope What it counts Typical owner Example limit
Management group Hierarchy depth/breadth Platform / governance MG depth (6 levels), MGs per tenant
Subscription Resource groups, role assignments Subscription owner ~980 RGs; ~4,000 role assignments
Subscription + region vCPUs, IPs, NICs, storage accts App / platform team vCPU family quotas; 250 storage accts
Resource Disks, NICs, sub-resources App team Data disks per VM SKU; subnets per VNet
Tenant Subscriptions, app registrations Identity / account team Subscriptions per EA; app regs

Core concepts

Five ideas make every later step obvious.

A quota is usage-versus-limit, per subscription, per region. Every adjustable quota is two numbers: current usage (currentValue) and limit; the portal draws them as a bar. Both are scoped to one subscription in one regionwesteurope and eastus vCPUs are entirely separate quotas. A request that raises one does nothing for the other, which explains most “but it worked in the other region” confusion.

vCPU quotas are layered: total regional, then per-family. Compute quota is not one number. There’s a Total Regional vCPUs limit for the whole region, and under it a per-VM-family limit (standardDSv5Family, standardFSv2Family, standardESv5Family). A deployment must satisfy both — you can have plenty of regional headroom and still fail because the specific family is capped. The error names the family precisely so you know which to raise. Spot vCPUs are a separate quota again, so heavy Spot use doesn’t eat your on-demand allowance.

Adjustable versus fixed is the first fork. Some ceilings are adjustable — you raise them by asking, mostly self-service. Others are fixed platform limits no request can move: max data disks on a VM size, NICs per SKU, the addressable space of a VNet CIDR, resource groups per subscription. When you hit a fixed limit the answer is never “request more” — it’s “choose a bigger SKU,” “split across more VNets,” or “use another subscription.”

The increase path forks on size and risk. Small routine bumps — a few more vCPUs in a family, a handful more public IPs — go through an automated approval system, often completing in seconds to minutes. Large jumps (hundreds of cores), constrained/new SKUs, GPU/HPC families, or capacity-pressured regions route to a human capacity team, taking hours to days. The Azure Quotas experience tries the automated path first and only falls back to a support request when it can’t self-approve.

Quotas are a capacity-planning input, not an incident. Treat quota like cost: a number you watch with a threshold, raise ahead of need, and request per region (especially the DR region). Azure surfaces usage for the major quotas as metrics, so you can alert at ~80% and request more before a launch — turning a deploy-time failure into a boring request done a week early.

The vocabulary in one table

Pin down the moving parts before the deep sections (the glossary repeats these for lookup):

Term One-line definition Scope Adjustable?
Quota Adjustable ceiling on a counted resource Subscription + region Yes (mostly)
Service limit Microsoft’s name for a quota (used in docs) Varies Yes if a quota
Hard limit Fixed platform/architectural boundary Resource / platform No
Usage (currentValue) How much of the quota you’ve consumed Subscription + region
Limit (limit) The ceiling you’re measured against Subscription + region Yes if a quota
Total Regional vCPUs All-family vCPU ceiling for a region Subscription + region Yes
VM-family quota vCPU ceiling for one family (e.g. DSv5) Subscription + region Yes
Spot vCPU quota Separate ceiling for Spot instances Subscription + region Yes
Azure Quotas The portal/API surface for viewing & raising
Quota request The ask to raise a limit Subscription + region
Auto-approval Self-service grant for small/routine asks

How Azure structures limits: quota vs hard limit

Before you read or raise anything, internalise the two buckets — the response to each is opposite.

Adjustable quotas — the things you can raise

An adjustable quota is a ceiling you can move on request — the numbers that scale with your footprint. The headline ones a typical team meets:

Quota Scope Common default (PAYG) Adjustable How you raise it
Total Regional vCPUs Subscription + region ~10–20 (varies) Yes Azure Quotas / az quota
Per-family vCPUs (e.g. standardDSv5Family) Subscription + region Low per family Yes Azure Quotas / az quota
Spot vCPUs (total + per-family) Subscription + region Separate, often low Yes Azure Quotas
Standard public IP addresses Subscription + region Tens Yes Azure Quotas / support
Public IP prefixes Subscription + region Few Yes Support
Network interfaces Subscription + region Hundreds–thousands Yes Support
Network security groups Subscription + region ~5,000 Yes (rarely needed) Support
Load balancers Subscription + region ~1,000 Yes Support
Storage accounts per region Subscription + region 250 (raise to 500) Yes Support
App Service plans / instances Subscription + region Varies by tier Partly Support

The defaults above are illustrative ranges, not promises — they vary by subscription type, region, offer and date. Always read your live value with the CLI in the next section. The point of the table is the shape: these are the levers you can pull.

Fixed limits — the things you design around

A fixed (hard) limit is structural — no request moves it; you change the design. The ones that bite teams most:

Fixed limit Approximate value Why it’s fixed What you do instead
Data disks per VM 4–64 (by SKU) Tied to the VM size’s I/O lanes Pick a larger SKU; pool disks
NICs per VM 2–8 (by SKU) Tied to the VM size Pick a larger SKU
Private IPs per NIC ~256 Platform Add NICs / subnets
Subnets per VNet ~3,000 Platform Fewer, bigger subnets
Addresses in a VNet Defined by CIDR (e.g. /16 = 65,536) IP math Add address spaces / VNets + peering
VNet peerings per VNet ~500 Platform Hub-spoke / mesh redesign
Resource groups per subscription ~980 Subscription structure Split into more subscriptions
Resources per resource group ~800 per type (varies) Subscription structure More RGs
Role assignments per subscription ~4,000 Identity platform Use groups, not per-user
Management group depth 6 levels Hierarchy design Flatten the tree
Subnets, NSG rules per NSG ~1,000 rules Platform ASGs to collapse rules

The tell for a fixed limit: it’s expressed as a maximum of a sub-resource per parent (disks per VM, subnets per VNet), or a structural cap (RGs per subscription). When you see that shape, there’s no “request increase” button. The decision rule:

If the limit is expressed as… It’s probably… Your move
“X per region for your subscription” An adjustable quota Request an increase
“X cores of family Y” An adjustable vCPU quota Request more cores
“Maximum X per VM / VNet / NIC” A fixed platform limit Choose a bigger parent SKU/CIDR
“X per subscription” (RGs, role asgmts) A structural limit Split scope / new subscription
“X per tenant” (subscriptions, MGs) A tenant-structural limit Talk to your account/EA team

Reading your usage before it bites

You cannot plan capacity you can’t see. Azure gives you the numbers in the portal, REST, and three CLI commands that cover 90% of what bites — get fluent in these and “surprise quota failure” mostly disappears.

vCPU usage with az vm list-usage

The single most useful quota command. It returns, for one region, every compute quota (total regional, every family, Spot) as currentValue vs limit:

# Every compute quota in a region: usage vs limit
az vm list-usage --location westeurope -o table

The output lists Total Regional vCPUs, Standard DSv5 Family vCPUs, etc., each with current/limit. To find what’s nearly full — the rows worth acting on — filter to where usage is a high fraction of the limit:

# Show only families where you're using > 80% of the limit
az vm list-usage --location westeurope --query \
  "[?limit > \`0\` && currentValue >= (limit * \`0.8\` | floor(@))].{name:localName, used:currentValue, limit:limit}" \
  -o table

Run it across the regions you actually deploy to, not just the one you live in:

for r in westeurope northeurope eastus; do
  echo "== $r =="
  az vm list-usage --location "$r" \
    --query "[?localName=='Total Regional vCPUs'].{region:'$r', used:currentValue, limit:limit}" -o table
done

Network usage with az network list-usages

The network quotas — public IPs, NICs, NSGs, load balancers, VNets — come from a different command, also per region:

# Network quotas (public IPs, NICs, NSGs, LBs, VNets) usage vs limit
az network list-usages --location westeurope -o table
# Just public IP addresses, the one that bites bulk deploys
az network list-usages --location westeurope \
  --query "[?contains(name.value,'PublicIPAddresses')].{name:name.localizedValue, used:currentValue, limit:limit}" \
  -o table

Storage account usage with az storage account

Storage accounts are capped per region (commonly 250, raisable to 500). There’s no dedicated list-usage, so count what you have against the known cap:

# Count storage accounts in this subscription (all regions), then per region
az storage account list --query "length(@)"
az storage account list --query "[?location=='westeurope'] | length(@)"

The unified az quota surface

The newer az quota command group reads (and writes) quotas across providers through the Microsoft.Quota resource provider, using the provider scope of the thing you’re querying. For compute in a region:

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

# List all compute quotas at this scope (usage + limit live behind quota + usages)
az quota list --scope "$SCOPE" -o table

# Current usage for the same scope
az quota usage list --scope "$SCOPE" -o table

# One specific quota by its resource name (e.g. total regional vCPUs)
az quota show --resource-name "cores" --scope "$SCOPE" -o json

The three commands you’ll actually reach for, side by side:

Command Covers Scope it needs Best for
az vm list-usage --location <r> All compute (vCPU) quotas A region Daily vCPU headroom check
az network list-usages --location <r> Public IPs, NICs, NSGs, LBs, VNets A region Network headroom before bulk deploy
az quota list / usage list / show Any provider via Microsoft.Quota A provider+region scope Programmatic / cross-provider reads and writes

A reading note: currentValue is live consumption (changes as you create/delete resources); limit only changes when a request is granted. vCPU quota counts allocated cores — deallocated VMs hold none — but other resources (public IPs, NICs) count whether attached or not.

Requesting an increase: the three paths

When the gap is too small, you raise the ceiling. Three routes, picked by whether you’re a human at a portal, a script, or a platform team codifying it.

Path A — the Azure Quotas portal (the default)

The modern Azure Quotas experience (search “Quotas” in the portal, or the My quotas blade) is the path for most people most of the time. It tries the automated approval first and only escalates to a support request if it can’t self-approve. The flow:

Step Action What you see
1 Open Quotas → choose a provider (e.g. Compute) A grid of quotas with usage bars
2 Filter by subscription, region, usage Narrowed list; sort by % used
3 Tick the quota row(s) you want to raise A pencil / Increase quota action appears
4 Enter the new limit (absolute value, not delta) A validation of feasibility
5 Submit Auto-approved in seconds, or a support request is created
6 Watch status Bar updates on approval; ticket if escalated

Two traps: the value you enter is the new total limit, not the additional amount (typing 10 when you have 8 leaves you at 10, not 18). And pick the right region in the filter — the most common failed request raises eastus when production is in westeurope.

Path B — the az quota CLI (scriptable, repeatable)

For automation, runbooks or speed, create or update a quota request with az quota, setting the new absolute limit at the provider+region scope:

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

# Raise the DSv5 family vCPU limit to 64 (new absolute value)
az quota create \
  --resource-name "standardDSv5Family" \
  --scope "$SCOPE" \
  --limit-object value=64 \
  --resource-type "dedicated"

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

Track the request and read the outcome:

# Status of submitted quota requests at this scope
az quota request status list --scope "$SCOPE" -o table

# Detail of one request (Accepted / InProgress / Succeeded / Failed)
az quota request status show --name <requestId> --scope "$SCOPE" -o json

The --resource-name is the quota’s internal name, not its display name — standardDSv5Family, cores (for total regional), standardFSv2Family, etc. Read it from az quota list --scope "$SCOPE" first if unsure; guessing the name is the usual cause of a create that 400s.

Path C — a support request (the human path for big asks)

When the automated path can’t self-approve — large core counts, constrained/GPU SKUs, capacity-pressured regions, or non-vCPU quotas that route to humans — you file a Service and subscription limits (quotas) support request. Also the path if you want a ticket trail. From the Quotas blade’s escalation, or directly:

# Open a quota-type support ticket (requires a support plan for faster SLA)
az support tickets create \
  --ticket-name "quota-dsv5-westeurope-2026-06" \
  --title "Increase DSv5 vCPU quota in West Europe to 256" \
  --severity "minimal" \
  --problem-classification "<quota-problem-classification-id>" \
  --quota-ticket-details '{"quotaChangeRequestSubType":"Compute","quotaChangeRequestVersion":"1.0","quotaChangeRequests":[{"region":"westeurope","payload":"{\"VMFamily\":\"standardDSv5Family\",\"NewLimit\":256}"}]}'

The SLA on a quota ticket depends on your support plan and the severity you set — see Azure Support Plans Compared: Severity Levels, Response Times and What to Buy. Quota requests are typically low/minimal severity — you’re not down, you’re planning ahead — so set severity honestly.

Which path, when

Situation Use Why
Routine vCPU/IP bump, you’re at a portal Quotas portal (A) Auto-approves in seconds
Same bump, but in a pipeline/runbook az quota (B) Scriptable, idempotent-ish, auditable
Hundreds of cores / GPU / constrained SKU Support request © Needs human capacity allocation
Quota with no self-service (some net/storage) Support request © Routes to capacity team
Codifying baseline quotas for new subs az quota in IaC/pipeline (B) Repeatable landing-zone step
You want a paper trail / SLA Support request © Ticket with tracked SLA

Will it auto-approve? The mental model

You can predict the path before you submit:

Factor Leans auto-approve Leans human review
Magnitude Small delta (e.g. +8–32 cores) Large jump (hundreds of cores)
SKU type Common GP family (Dsv5, etc.) GPU / HPC / specialty / constrained
Region capacity Healthy region Capacity-pressured region
Resource kind vCPU, public IP Some networking/storage/PaaS
Current usage Plenty of regional headroom Pushing total regional limit too
Account standing Established billing history Brand-new / trial subscription

The takeaway: ask early and in modest steps and the system mostly says yes instantly. Ask for a 20× jump the night before launch in a hot region and you’ve signed up for a human, a queue and a wait.

Scope: per-subscription, per-region, per-resource-group

Most quota confusion is a scope mistake. Three rules end it.

vCPU and network quotas are per subscription AND per region. Your 200-core westeurope quota grants nothing in northeurope. When you design multi-region — active-active or a DR secondary — request quota in every region you’ll deploy to, including the one you only fail over to. A failover that can’t allocate cores because the secondary region was never quota-provisioned is a classic, avoidable DR-test failure. (See Azure Regions and Availability Zones: Designing for Resilience.)

Some limits are per subscription, region-agnostic. Resource groups (~980), role assignments (~4,000), and structural caps count across the whole subscription. You don’t “request more eastus resource groups”; you stay under the cap or split into another subscription.

A few limits are per resource group or per resource. Resources of one type per RG, sub-resources per parent (subnets per VNet, disks per VM). These you manage by structure, not request.

Limit Per subscription? Per region? Per RG / resource?
vCPUs (total + family) Yes Yes No
Public IPs, NICs, NSGs Yes Yes No
Storage accounts Yes Yes No
Resource groups Yes No No
Role assignments Yes No No
Subnets per VNet No No Per VNet
Data disks per VM No No Per VM

The operational rule that falls out: inventory the regions in your architecture, then run the usage check in each one. A one-region audit on a multi-region design is false comfort.

Architecture at a glance

Hold this mental model and quotas stop being mysterious. Picture three nested rings. The outermost is the tenant, where a few truly structural limits live (how many subscriptions, how deep the management-group tree goes) — set by your Microsoft agreement and governance design, moving rarely if ever. Inside it sit your subscriptions, each a billing-and-scope boundary carrying the region-agnostic structural caps: resource groups, role assignments, resources of a kind. These you manage by splitting — when one fills up, the answer is another subscription, not a request.

Zoom into a subscription and it fans out into regionsthis is where the quotas you fight with live. For each region the subscription holds an independent set of meters: a Total Regional vCPU ceiling, a stack of per-family vCPU ceilings beneath it, a separate Spot stack, and the network meters (public IPs, NICs, NSGs, load balancers) — every one a currentValue/limit pair specific to this subscription in this region. Finally, inside each region, individual resources carry their own fixed sub-limits: a VM’s disk and NIC ceilings, a VNet’s subnet and peering ceilings, baked into the SKU or CIDR.

Read a deployment failure by walking inward. QuotaExceeded naming a vCPU family? The region ring — adjustable, request more in that region. “Maximum data disks” or “subnets per VNet”? The innermost resource ring — fixed, change the parent. “Resource groups per subscription”? The subscription ring — structural, split scope. The error tells you which ring you’re in; the ring tells you whether to file a request or redesign. This nested-rings picture is the model — more useful in your head than drawn on a page.

Real-world scenario

Northwind Retail runs e-commerce out of westeurope, with DR in northeurope. Steady-state they run ~120 vCPUs across Dsv5 web/API tiers and Esv5 workers, inside a regional quota raised to 256 cores a year earlier. Two things were about to collide: a Black Friday scale-out to ~400 vCPUs at peak, and a long-scheduled DR failover test the same quarter.

The first warning came from a habit, not an incident. Platform engineer Sana ran the weekly headroom check — a scheduled pipeline doing az vm list-usage across both regions, flagging anything over 70% — and westeurope Dsv5 showed 198 / 256, climbing as they pre-staged. At 77% the flag fired. Six weeks out, with no outage, she raised westeurope total regional vCPUs and standardDSv5Family to 512 via az quota update. Both auto-approved within two minutes — modest steps on an established subscription, common GP family, healthy region. No ticket, no wait.

The DR region is where the lesson landed. The same audit script ran against northeurope and reported standardDSv5Family: 0 / 10. They had never requested compute quota in the failover region — every deploy had only ever touched westeurope. Had the failover test run, it would have tried to stand up ~120 cores of Dsv5 and hit QuotaExceeded at core eleven, failing the DR test not because of replication or DNS but because the secondary region had a default quota. Sana requested northeurope to match the primary’s steady-state plus burst — 512 cores. This one did not auto-approve: a 50× jump on a region with zero history routed to a support request, severity minimal, granted in about a day on their Standard support plan. Because it was raised six weeks early, the wait was invisible.

Black Friday came. Autoscale took the web tier from 120 to 390 vCPUs at peak; every scale-out succeeded because the ceiling was 512 with room to spare. The mid-quarter DR test failed over to northeurope, allocated its full footprint, and passed. The total cost of avoiding two embarrassing failures: one scheduled az vm list-usage pipeline, two requests filed weeks early, and the discipline to check both regions. The fix was never technical; it was temporal. They looked early.

Advantages and disadvantages

Quotas are a feature, not just an obstacle — they protect you as much as they constrain you.

Quotas (the system) What it means for you
Advantage Cap blast radius of a bug/compromise A runaway script or leaked credential can’t spin up unlimited resources
Advantage Protect shared regional capacity Your deploy isn’t starved because another tenant grabbed everything
Advantage Force capacity planning You’re nudged to think about footprint before you hit the wall
Advantage Cost guardrail (indirect) A low vCPU quota is a soft brake on runaway compute spend
Advantage Auto-approval makes routine asks instant Small bumps don’t slow you down
Disadvantage Deploy-time failures if unplanned QuotaExceeded mid-pipeline leaves half-built infra
Disadvantage Per-region scoping is a footgun Easy to forget the DR region and fail a failover
Disadvantage Big asks need humans + time Hundreds of cores in a hot region can take days
Disadvantage Defaults are low for new subscriptions First real deploy on a fresh sub often hits the wall
Disadvantage Fixed limits can force a redesign Some “limits” can’t be raised at all — only architected around

The advantages matter most to platform and security teams: a cheap, automatic guardrail against worst-case spend or compromise, costing nothing to keep. The disadvantages matter most to delivery teams under time pressure: treat quota as a deploy-time discovery and it’s pure friction, and the per-region footgun will eventually bite a DR test. The whole art is converting the disadvantages into non-events by adopting the advantage-side mindset — plan capacity, watch usage, request early, per region.

Hands-on lab

The centerpiece. You’ll read your quotas end to end in the portal and with az, request an increase via the CLI, encode a usage-alert with Bicep, and tear down — all read-only or low-cost, safe on any subscription. Most steps cost nothing (reads and a metric alert are effectively free; the one optional VM is pennies and you delete it). Expected output is shown at each step so you can validate as you go.

Prerequisites for the lab:

Requirement Why Check
An Azure subscription Everything is per-subscription az account show
az ≥ 2.50 with the quota extension az quota lives in an extension az version; az extension add --name quota
Reader (to read) / Contributor+ (to request) Reads need Reader; requests need write az role assignment list --assignee <you>
A region you deploy to (pick one) Quotas are per-region choose e.g. westeurope
Cloud Shell or local terminal logged in To run az az login

Step 0 — Set up and pin your context

# Log in (skip in Cloud Shell) and pin the subscription + region you'll use
az login
az account set --subscription "<your-subscription-id-or-name>"

# Variables we'll reuse
SUB=$(az account show --query id -o tsv)
LOC="westeurope"
echo "Subscription: $SUB   Region: $LOC"

# Ensure the quota CLI extension is present
az extension add --name quota --only-show-errors || az extension update --name quota

Expected output: your subscription id and chosen region echoed; the extension installs or reports already-installed. If az account show errors, you’re not logged in.

Step 1 — Read vCPU usage in the portal

  1. In the portal, search Quotas and open it.
  2. Choose the Compute provider tile (or My quotas → filter Provider = Microsoft.Compute).
  3. Set Subscription to yours and Region to your chosen region.
  4. Sort by Usage descending.

Expected output: a grid of compute quotas, each with a usage bar showing current vs limit. Note the row Total Regional vCPUs and your most-used family (e.g. Standard DSv5 Family vCPUs). On a quiet subscription many bars are near-empty — that’s fine; you’re learning to read them.

Validate: you can point to one quota and say “I’m using N of M here.” No usage bar? You’re probably on the old “Usage + quotas” blade — use the dedicated Quotas service instead.

Step 2 — Read the same numbers from the CLI

# Full compute quota table for your region
az vm list-usage --location "$LOC" -o table

Expected output: rows like:

Name                          CurrentValue    Limit
----------------------------  --------------  -------
Total Regional vCPUs          0               10
Standard DSv5 Family vCPUs    0               10
Standard FSv2 Family vCPUs    0               10
...

Now isolate the high-usage rows you’d actually act on:

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

Expected output: only families at ≥50% (often empty on a quiet sub — a valid result meaning lots of headroom). Lower the 0.5 to surface more rows for practice.

Validate: the portal numbers from Step 1 match the CLI numbers for the same region — same source, identical values.

Step 3 — Read network and storage usage

# Network quotas (public IPs, NICs, NSGs, LBs, VNets)
az network list-usages --location "$LOC" -o table

# Public IP line specifically
az network list-usages --location "$LOC" \
  --query "[?contains(name.value,'PublicIPAddresses')].{name:name.localizedValue, used:currentValue, limit:limit}" -o table

# Storage accounts: count vs the per-region cap
az storage account list --query "[?location=='$LOC'] | length(@)"

Expected output: a network usage table; a single public-IP row (e.g. Public IP Addresses 0 <limit>); and a storage-account count for the region (often 0 on a fresh sub) — you’re validating you’re well under the 250→500 cap.

Validate: you now have, for one region, vCPU + network + storage headroom in three commands. That’s the daily check.

Step 4 — Use the unified az quota surface

SCOPE="/subscriptions/$SUB/providers/Microsoft.Compute/locations/$LOC"

# All compute quotas via the Quota RP
az quota list --scope "$SCOPE" -o table

# Live usage at the same scope
az quota usage list --scope "$SCOPE" -o table

# One specific quota by internal name — total regional vCPUs is "cores"
az quota show --resource-name "cores" --scope "$SCOPE" -o json \
  --query "{name:name, limit:properties.limit.value, unit:properties.unit}"

Expected output: quota list returns quotas with their internal resource names (cores, standardDSv5Family, …) — note these; you need the exact name to request a change. quota show for cores returns a small JSON with the limit value.

Validate: you can map a display name (“Total Regional vCPUs”) to its internal name (cores) — the mapping most failed CLI requests get wrong.

Step 5 — Request a quota increase via CLI (small, auto-approvable)

Pick a quota with plenty of headroom and request a modest bump so it’s likely to auto-approve and costs nothing (raising a limit creates no resources). We’ll nudge a family’s vCPU limit up slightly.

SCOPE="/subscriptions/$SUB/providers/Microsoft.Compute/locations/$LOC"

# Read the current limit for the family first (so you set a sane new absolute value)
CUR=$(az quota show --resource-name "standardDSv5Family" --scope "$SCOPE" \
        --query "properties.limit.value" -o tsv)
echo "Current DSv5 limit: $CUR"

# Request a new ABSOLUTE limit a bit higher (e.g. current + 10, capped low to stay auto-approvable)
NEW=$(( CUR + 10 ))
az quota create \
  --resource-name "standardDSv5Family" \
  --scope "$SCOPE" \
  --limit-object value=$NEW \
  --resource-type "dedicated" \
  || az quota update --resource-name "standardDSv5Family" --scope "$SCOPE" --limit-object value=$NEW

Expected output: a JSON request object with provisioningState of Accepted, InProgress, or Succeeded. Small bumps on healthy regions frequently flip to Succeeded within a minute or two. Track it:

az quota request status list --scope "$SCOPE" \
  --query "[].{name:name, state:properties.provisioningState, created:properties.requestSubmitTime}" -o table

Expected output: your request with a state column. Succeeded = granted; InProgress = still deciding; Failed = read properties.message (most often a bad resource-name or an over-large jump routed to humans).

Validate: re-run Step 4’s az quota show for standardDSv5Family. If granted, the limit now reflects $NEW. Still pending is a valid outcome too — you’ve submitted a request, which is the skill.

If your org restricts quota changes, this step may be denied by policy — that’s expected. The objective is knowing the command and reading the status; you can stop after request status list.

Step 6 — Alert before you exhaust: a usage metric alert in Bicep

Turn quota into a watched number. Azure exposes compute quota usage as a metric you can alert on. Here’s a metric alert (the free tier covers a few alert rules) that fires when regional vCPU usage crosses a threshold. Save as quota-alert.bicep:

@description('Region whose vCPU usage we watch')
param location string = 'westeurope'

@description('Action group to notify (create or reference an existing one)')
param actionGroupId string

@description('Fire when this many vCPUs are in use in the region')
param vcpuThreshold int = 200

// Quota usage is published under the subscription/region as a metric on the
// Microsoft.Capacity/resourceProviders usages namespace. We alert on it.
resource quotaAlert 'Microsoft.Insights/metricAlerts@2018-03-01' = {
  name: 'alert-regional-vcpu-usage-${location}'
  location: 'global'
  properties: {
    description: 'Warn when regional vCPU usage approaches the quota limit'
    severity: 2
    enabled: true
    scopes: [
      subscription().id
    ]
    evaluationFrequency: 'PT15M'
    windowSize: 'PT1H'
    targetResourceType: 'Microsoft.Compute/locations/usages'
    targetResourceRegion: location
    criteria: {
      'odata.type': 'Microsoft.Azure.Monitor.SingleResourceMultipleMetricCriteria'
      allOf: [
        {
          name: 'TotalRegionalvCPUs'
          metricNamespace: 'Microsoft.Compute/locations/usages'
          metricName: 'Usage'
          operator: 'GreaterThan'
          threshold: vcpuThreshold
          timeAggregation: 'Maximum'
          criterionType: 'StaticThresholdCriterion'
        }
      ]
    }
    actions: [
      {
        actionGroupId: actionGroupId
      }
    ]
  }
}

output alertName string = quotaAlert.name

Deploy it (you need an action group id — reuse one from your cost/monitor alerts; see How to Create Your First Metric Alert and Action Group for Email, SMS and Push):

# Validate first (preflight), then deploy at subscription scope
AG_ID=$(az monitor action-group show -n <your-action-group> -g <rg> --query id -o tsv)

az deployment sub validate \
  --location "$LOC" \
  --template-file quota-alert.bicep \
  --parameters location="$LOC" actionGroupId="$AG_ID" vcpuThreshold=200

az deployment sub create \
  --location "$LOC" \
  --template-file quota-alert.bicep \
  --parameters location="$LOC" actionGroupId="$AG_ID" vcpuThreshold=200

Expected output: validate returns no errors; create returns Succeeded and the alert name. The alert now sits at subscription scope and evaluates regional vCPU usage hourly.

Validate: Monitor → Alerts → Alert rules shows alert-regional-vcpu-usage-westeurope, enabled. You’ve converted “quota” into “monitored capacity.”

The exact metric namespace/name for quota usage can differ by provider and evolve; if the alert won’t validate, list metrics for the usage resource with az monitor metrics list-definitions --resource <usage-resource-id> and match the real metricName. The pattern — alert on usage, fire before the limit — is the durable lesson.

Step 7 — (Optional) Make usage move, then watch it

To see currentValue change, create one tiny VM, re-read usage, then delete it — a few pennies for minutes of runtime, so delete promptly.

RG="rg-quota-lab"
az group create -n "$RG" -l "$LOC"

# Before: note DSv5 usage
az vm list-usage --location "$LOC" \
  --query "[?contains(localName,'DSv5')].{name:localName, used:currentValue, limit:limit}" -o table

# Create one small Dsv5 VM (2 vCPUs) — this consumes vCPU quota while allocated
az vm create -g "$RG" -n vm-quota-lab \
  --image Ubuntu2204 --size Standard_D2s_v5 \
  --admin-username azureuser --generate-ssh-keys --no-wait

# Give it ~60–90s to allocate, then re-read (usage should rise by 2)
az vm list-usage --location "$LOC" \
  --query "[?contains(localName,'DSv5')].{name:localName, used:currentValue, limit:limit}" -o table

Expected output: the used value for the DSv5 family increases by 2 (the D2s_v5’s vCPUs) once allocated — quota consumption you can see.

Validate: the DSv5 bar in the portal (Step 1) nudges up too. You’ve watched usage rise toward a limit in real time.

Step 8 — Teardown

# Delete the optional VM + its RG (releases the 2 vCPUs of quota)
az group delete -n "$RG" --yes --no-wait

# Delete the metric alert if you don't want to keep it
az monitor metrics alert delete -n "alert-regional-vcpu-usage-${LOC}" -g <rg-where-deployed> 2>/dev/null || \
az deployment sub create --location "$LOC" --template-file /dev/stdin <<< '{}' 2>/dev/null

Expected output: the resource group deletes (vCPU usage returns to its prior value within a minute or two); the alert is removed. The quota increase from Step 5 stays — limits don’t auto-revert and a higher limit costs nothing, so there’s no need to undo it.

Validate: az vm list-usage --location "$LOC" shows DSv5 currentValue back where it started; the lab alert is gone. You’ve completed a full read → request → alert → teardown cycle.

A recap of what each step proved:

Step You learned to… Cost
0–1 Pin context; read quotas with the portal usage bar Free
2–3 Read vCPU, network, storage usage from az Free
4 Map display names → internal names via az quota Free
5 Submit and track a quota increase request Free
6 Alert before exhaustion with Bicep Free (alert rule)
7 Watch usage rise against a limit in real time Pennies (deleted)
8 Tear down cleanly; know what persists Free

Common mistakes & troubleshooting

The failure modes here are mostly conceptual — wrong scope, wrong number, wrong kind of limit — each with a precise tell and fix.

# Symptom Root cause How to confirm Fix
1 QuotaExceeded for a VM family mid-deploy Family vCPU limit too low in this region Error text names family + “Current Limit”; az vm list-usage Request that family’s quota up in this region
2 Increase request “did nothing” Entered a delta as the new limit Compare requested value to old limit Re-request the absolute target (old + needed)
3 Request rejected / routed to support unexpectedly Big jump / GPU / constrained SKU / hot region az quota request status show message File a support request; ask in smaller steps
4 Plenty of total cores but still fails Per-family quota exhausted under the regional total az vm list-usage shows family near limit Raise the family quota, not just total
5 Worked in region A, fails in region B Quotas are per region; B never raised Run az vm list-usage in B Request the same quota in region B
6 DR failover can’t allocate compute DR region on default quota az vm list-usage --location <dr> shows low limit Pre-provision quota in the DR region
7 Autoscale stops adding instances Hit regional/family vCPU ceiling Plan instance count flat; usage at limit Raise vCPU quota; or use a smaller SKU
8 “Max data disks” / “max NICs” error Fixed SKU limit, not a quota Docs/error say “per VM” Choose a larger VM SKU — no request exists
9 Can’t add another resource group Hit ~980 RGs per subscription az group list --query "length(@)" Split into a new subscription
10 az quota create returns 400 Wrong internal resource name az quota list --scope ... shows real names Use the exact internal name (cores, standardDSv5Family)
11 Spot deploy fails though on-demand quota is fine Spot vCPUs are a separate quota az vm list-usage shows Spot rows Raise the Spot vCPU quota specifically
12 Bulk Terraform apply rolls back partway Tripped public-IP/NIC quota mid-run az network list-usages; check failed resource Raise the network quota; or stagger the apply

Three of these deserve a closer note:

Best practices

Security notes

Quotas are themselves a security control, and managing them touches identity. A low default vCPU quota is a built-in brake on a compromised credential or runaway automation — an attacker (or a buggy loop) trying to spin up a crypto-mining fleet hits your ceiling fast — so don’t reflexively max out every quota; leave them sized to real need plus reasonable headroom so the ceiling stays meaningful.

Changing a quota or opening a quota ticket is privileged: grant Microsoft.Quota/* and Microsoft.Support/* narrowly (platform/SRE, not every Contributor), so a developer can read usage with Reader but only a platform role can request increases — and scope the monitoring identity that runs the scheduled check to Reader only. (See Azure Policy Effects Explained: Deny, Audit, Modify and DeployIfNotExists for codifying such guardrails.) A sudden increase can signal misuse, so collect Microsoft.Quota and support-ticket operations from the Activity Log to keep increases traceable to a person and a justification.

Concern Control How
Runaway/compromise blast radius Keep quotas sized to need Don’t max everything; review headroom
Who can raise quotas RBAC least privilege Limit Microsoft.Quota/* + support roles to platform team
Read access for monitoring Reader-only identity Scope the headroom-check principal to Reader
Traceability Activity Log / audit Collect Microsoft.Quota + support ops to a workspace

Cost & sizing

The reassuring part: raising a quota costs nothing. A quota is a ceiling, not a reservation — you’re billed for resources you create, never for headroom you don’t use. A 1,000-core quota with 50 cores running costs exactly the same as a 100-core quota with 50 cores running.

The cost relationship is indirect, two ways. A quota is a soft spending brake: a low vCPU quota means a runaway autoscale can only spend so much before hitting the wall — which is why you don’t max out quotas you don’t need. And the real cost lives in the resources you provision up to the quota, so when you plan an increase you’re implicitly planning the spend that fills it — pair the request with a budget (Azure Cost Management for Beginners: Budgets, Alerts and Cost Analysis in Your First 30 Days) and a look at Azure Advisor for Cost: Acting on Rightsizing and Idle-Resource Recommendations so the cores you unlock are right-sized.

What drives the bill behind a vCPU quota, in rough INR/USD orders of magnitude (varies — always price your real SKU):

Driver Rough cost order Notes
The quota itself ₹0 / $0 Ceilings are free; you pay for usage
On-demand vCPU (e.g. D2s_v5, 2 vCPU) ~₹7–10k / ~$85–120 per month if always on Per-second billed; deallocate to stop
Spot vCPU Up to ~90% cheaper than on-demand Separate quota; can be evicted
Public IP (Standard) ~₹250–400 / ~$3–5 per IP/month Counts against IP quota whether attached or not
Storage account Pennies + per-GB/transaction Account is cheap; data/ops drive cost
The metric alert from the lab Free–negligible A few alert rules are free; high volume is cents

The free-tier angle: Free/Trial and fresh PAYG subscriptions start with low quotas by design (often ~10 total regional vCPUs), so your first real workload almost always needs a bump — budget a few minutes for it in onboarding. Right-sizing rule of thumb: request steady-state + planned burst + ~20–30% headroom, not a round-number maximum — a wildly oversized quota is a wider blast radius for zero saving, while a too-tight one fails your next scale-out.

Interview & exam questions

Q1. What is the difference between an Azure quota and a hard limit? A quota (service/usage limit) is an adjustable ceiling on a counted resource — vCPUs, public IPs, storage accounts — you can raise on request, often self-service. A hard limit is a fixed boundary — data disks per VM SKU, addresses in a VNet CIDR, RGs per subscription — that no request changes; you design around it. Telling them apart from the error is the key skill. Relevant to AZ-104 and AZ-305.

Q2. A deployment fails with QuotaExceeded for standardDSv5Family. Walk through your response. Read the error for family, current limit, usage and additional-required. Confirm with az vm list-usage --location <region>. Request that family’s quota to the needed total plus headroom, in that region, via the Quotas portal or az quota create/update with an absolute value. Verify and re-run the deploy.

Q3. Why might a deployment fail on vCPU quota even though “Total Regional vCPUs” has headroom? vCPU quota is layered: a regional total and a per-family limit beneath it, and a deployment must satisfy both. The family (e.g. DSv5) can be exhausted while the regional total still has room. Fix by raising the family quota, not just the total.

Q4. Your DR failover test fails to allocate compute in the secondary region. Why, and how do you prevent it? Quotas are per region, and the DR region was never quota-provisioned — it sits on default low limits. Treat quota as part of DR readiness: pre-request the full failover footprint in the secondary region and verify with az vm list-usage --location <dr-region> before the test.

Q5. Which quota requests auto-approve, and which need a human? Small routine bumps (a modest number of vCPUs in a common GP family, a few public IPs) on established subscriptions in healthy regions usually auto-approve in seconds to minutes. Large jumps (hundreds of cores), GPU/HPC/constrained SKUs, capacity-pressured regions, and some networking/storage quotas route to a human capacity team and take longer.

Q6. What scoping applies to vCPU quotas versus resource-group limits? vCPU and most network quotas are scoped to subscription + region — independent per region. The RGs-per-subscription limit (~980) is per subscription, region-agnostic. Raising one region’s vCPU quota does nothing for another; the RG cap is solved by a new subscription, not a request.

Q7. How do you read current usage against a quota from the CLI? az vm list-usage --location <region> for compute (returns currentValue vs limit per family and the regional total); az network list-usages --location <region> for public IPs/NICs/NSGs/LBs; and az quota list/usage list/show --scope <provider+region> for a unified, cross-provider view including the internal resource names needed to request changes.

Q8. Does raising a quota cost money? No. A quota is a ceiling, not a reservation — you’re billed only for resources you create. A higher quota with the same running resources costs the same. The cost is indirect (a higher quota enables more spend), so pair increases with budgets and right-sizing.

Q9. You hit “maximum data disks” on a VM. Do you request a quota increase? No — that’s a fixed limit tied to the VM SKU, not a quota. There’s no increase to request. Move to a larger size that supports more data disks, or restructure storage (fewer, larger disks).

Q10. Why are Spot vCPUs sometimes a separate problem from on-demand? Spot has its own vCPU quota (total regional + per-family Spot), distinct from on-demand. A subscription can have ample on-demand headroom but a near-zero Spot quota, so a Spot batch/build fleet fails while regular VMs deploy fine. Raise the Spot quota specifically.

Q11. What’s the recommended way to avoid quota-driven deploy failures operationally? Make a per-region usage check (az vm list-usage / az network list-usages) a scheduled job and pipeline pre-flight, alert at ~70–80% of critical quotas, and request increases ahead of need in every region you deploy to.

Q12. How does your support plan relate to quota increases? Routine bumps go through automated self-service regardless of plan. A large ask that routes to a support request has an SLA driven by your support plan and severity. If same-day large grants matter to your launches, a faster-SLA plan is the enabler.

Quick check

  1. You enter 10 as the new limit for a family currently at limit 8 and you needed 8 more. What did you just do wrong, and what should you have entered?
  2. az vm list-usage shows Total Regional vCPUs at 40/256 but your DSv5 deploy still fails on quota. What’s the cause and the fix?
  3. Name three things that are fixed limits (no increase possible) and what you do instead for each.
  4. Your app runs fine in westeurope but the DR deploy in northeurope fails to allocate cores. Why?
  5. Which CLI command shows public-IP usage against its limit, and at what scope?

Answers

  1. You set the new absolute limit to 10 (only +2), not the +8 you needed — the field is an absolute total, not a delta. You should have entered 16 (current 8 + needed 8), ideally 32 for headroom.
  2. The per-family DSv5 quota is exhausted even though the regional total has room — both must be satisfied. Fix: request the standardDSv5Family quota up in westeurope (not just the regional total).
  3. Examples: data disks per VM (→ choose a larger SKU), addresses in a VNet /16 (→ add address space / more VNets + peering), resource groups per subscription ~980 (→ new subscription). All are structural; no request applies.
  4. Quotas are per region; northeurope was never quota-provisioned and sits on default low limits. Request the DSv5/regional quota in northeurope and verify with az vm list-usage --location northeurope.
  5. az network list-usages --location <region> (filter for PublicIPAddresses) — scope is subscription + region.

Glossary

Next steps

AzureQuotasLimitsSubscriptionsCapacity PlanningAzure CLIGovernanceCost Management
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