Throttling and quotas: HTTP 429 and snapshot limits

Azure throttling 429 errors (“Too Many Requests”) mean you asked an Azure service for more than it will accept in a short time. Azure protects every API with limits: Resource Manager, Resource Graph, Key Vault, Storage and the disk snapshot service all have them. Scripts, backup software and monitoring tools hit them far more often than people do.

Throttling is not an outage. The service is telling you to slow down, and usually how long to wait. The wrong reaction, retrying immediately and harder, makes it worse; the right one is to back off and spread the work.

In this lab you read the rate-limit headers Azure already sends, take incremental snapshots until you hit the per-disk limit, and then trigger real 429s from Resource Graph and fix them with a retry function that respects the wait time.

TimeYou needCostBy the end you can
About 45 minutesA free account, the CLI and PowerShellUnder 1 US cent: a 4 GiB disk and a few snapshots for an hourRead rate-limit headers, respect snapshot limits, and write retry logic that backs off on 429

What it is, in one line

A 429 means an Azure API is limiting how fast you call it; the fix is to slow down and retry after the time it tells you.

In Simple Words

The ATM queue

An ATM lets each card make a few withdrawals in a short time. Try a sixth time in one minute and the screen says, “Please wait 60 seconds.” The bank isn’t broken; it’s protecting itself and everyone else in the queue.

If you keep jamming the card in every second, the bank may lock you out for longer. If you wait the 60 seconds, the next withdrawal works.

A 429 is that screen. The Retry-After or quota-reset header is the “wait 60 seconds” part, and good scripts read it.

Engineer's View

Limits you meet in daily operations

  • Azure Resource Manager: requests are limited per subscription and principal with a token bucket per region. For subscription reads, the bucket holds about 250 requests and refills about 25 per second; writes and deletes have smaller buckets. Responses carry x-ms-ratelimit-remaining-* headers.
  • Resource Graph: about 15 queries per 5 seconds per user by default. Headers x-ms-user-quota-remaining and x-ms-user-quota-resets-after tell you where you stand.
  • Incremental disk snapshots: up to 7 per disk every five minutes, and up to 500 per disk in total at any time.
  • Service limits: Key Vault, Storage and others have their own per-vault or per-account limits and return 429 or 503 ServerBusy.
  • Retry-After: many 429 responses say how many seconds to wait. SDKs and the Azure CLI already retry with backoff; your own loops and parallel jobs usually don’t.
  • Backup tools: Commvault, Rubrik and similar products snapshot many disks through the same APIs, so their concurrency settings decide whether they hit these limits.

How it works, step by step

What happens when a script sends 30 Resource Graph queries in a few seconds:

Azure throttling 429: how a script should back off
  1. Each query is checked against the caller’s quota window, about 15 per 5 seconds.
  2. The first queries succeed; each response shows x-ms-user-quota-remaining going down.
  3. When the quota is used up, Resource Graph returns HTTP 429 with x-ms-user-quota-resets-after, for example 00:00:03.
  4. A naive script counts these as failures and moves on, or retries at once and gets more 429s.
  5. A good script waits for the reset time, then continues, and every query eventually succeeds.

The same pattern applies to every Azure API: watch the remaining-quota headers, back off on 429, and add jitter so many workers don’t retry at the same moment.

Azure Resource Manager, Resource Graph and snapshot limits

For long-running jobs, the real fix is usually design: fewer, bigger queries, caching, schedules that spread work out, and concurrency limits in tools.

Control concurrency in your own scripts

Most 429s from home-made automation come from parallel loops with no ceiling. PowerShell 7 makes parallel work easy, and also makes the ceiling easy to set. This runs a command for many VMs, but never more than four at a time, with a short random pause so workers don’t start in lockstep:

$vms = az vm list --query "[].{rg:resourceGroup, name:name}" -o json | ConvertFrom-Json
$vms | ForEach-Object -Parallel {
    Start-Sleep -Milliseconds (Get-Random -Minimum 200 -Maximum 1500)
    az vm get-instance-view --resource-group $_.rg --name $_.name --query "instanceView.statuses[1].displayStatus" -o tsv
} -ThrottleLimit 4

The random sleep is the jitter. Without it, every retry and every new worker hits the API in the same second, which is how a small burst turns into a long wave of throttling. Four at a time is a starting point: raise it slowly while you watch the remaining-quota headers, and lower it the moment 429s appear.

Read Azure limits and hit two of them on purpose

1Read the limits Azure already tells you

Run a normal command with debug output and pick out the rate-limit headers:

az group list --debug 2>&1 | Select-String 'x-ms-ratelimit-remaining' | Select-Object -First 3

You should see something like this (numbers vary):

DEBUG: cli.azure.cli.core.sdk.policies:     'x-ms-ratelimit-remaining-subscription-reads': '249'
DEBUG: cli.azure.cli.core.sdk.policies:     'x-ms-ratelimit-remaining-subscription-global-reads': '15999'
DEBUG: cli.azure.cli.core.sdk.policies:     'x-ms-ratelimit-remaining-tenant-reads': '249'

Every Resource Manager response carries these. A script that logs them sees throttling coming long before it happens.

2A disk and its first incremental snapshot

Create a small disk and take one incremental snapshot:

az group create --name rg-saat-l68 --location centralindia --tags course=saatvedha-azure lesson=68 -o none
az disk create --resource-group rg-saat-l68 --name disk-l68 --size-gb 4 --sku StandardSSD_LRS -o none
$diskId = az disk show --resource-group rg-saat-l68 --name disk-l68 --query id -o tsv
az snapshot create --resource-group rg-saat-l68 --name snap-l68-0 --source $diskId --incremental true --query "{name:name, incremental:incremental, state:provisioningState}" -o table

You should see something like this:

Name        Incremental    State
----------  -------------  ---------
snap-l68-0  True           Succeeded

3Save the two scripts

Save this as C:\azure-lab\l68\Test-GraphBurst.ps1:

# SaatVedha Azure Lesson 68: send 30 Resource Graph queries as fast as possible
$token = az account get-access-token --resource https://management.azure.com --query accessToken -o tsv
$headers = @{ Authorization = "Bearer $token"; 'Content-Type' = 'application/json' }
$url = 'https://management.azure.com/providers/Microsoft.ResourceGraph/resources?api-version=2022-10-01'
$body = '{"query":"Resources | summarize count()"}'
$ok = 0; $throttled = 0
foreach ($i in 1..30) {
    try { Invoke-RestMethod -Method Post -Uri $url -Headers $headers -Body $body | Out-Null; $ok++ }
    catch { if ($_.Exception.Response.StatusCode.value__ -eq 429) { $throttled++ } else { throw } }
}
"OK: $ok   Throttled (429): $throttled"

And this as C:\azure-lab\l68\Invoke-GraphWithRetry.ps1:

# SaatVedha Azure Lesson 68: Resource Graph query that backs off on HTTP 429
function Invoke-GraphWithRetry {
    param([Parameter(Mandatory)][string]$Query, [int]$MaxTries = 6)
    $token = az account get-access-token --resource https://management.azure.com --query accessToken -o tsv
    $headers = @{ Authorization = "Bearer $token"; 'Content-Type' = 'application/json' }
    $url = 'https://management.azure.com/providers/Microsoft.ResourceGraph/resources?api-version=2022-10-01'
    $body = @{ query = $Query } | ConvertTo-Json
    for ($try = 1; $try -le $MaxTries; $try++) {
        try { return Invoke-RestMethod -Method Post -Uri $url -Headers $headers -Body $body }
        catch {
            if ($_.Exception.Response.StatusCode.value__ -ne 429) { throw }
            $wait = [math]::Min(30, [math]::Pow(2, $try))
            $reset = $_.Exception.Response.Headers['x-ms-user-quota-resets-after']
            if ($reset) { $wait = [math]::Ceiling(([timespan]$reset).TotalSeconds) + 1 }
            Write-Host "429 received, waiting $wait s (try $try of $MaxTries)"
            Start-Sleep -Seconds $wait
        }
    }
    throw "Still throttled after $MaxTries tries"
}
Resource group with a disk and its first snapshot for the throttling lab
rg-saat-l68 with disk-l68 and its first incremental snapshot.
Resource Manager rate-limit headers in CLI debug output
Azure tells you how much room you have left.

✅ Checkpoint: You can see ARM’s remaining-request headers, you have a disk with one incremental snapshot, and both scripts are saved.

Break It, Then Fix It

Keep the disk and snapshot for break 1.

1Break it: a snapshot loop that runs too fast

A home-made backup script takes a snapshot of the same disk every few seconds. Try eight in a row:

foreach ($i in 1..8) {
    $r = az snapshot create --resource-group rg-saat-l68 --name "snap-l68-$i" --source $diskId --incremental true --query provisioningState -o tsv 2>&1
    "$i : $r"
}

You should see the first few succeed and the last one or two fail, something like this:

1 : Succeeded
2 : Succeeded
3 : Succeeded
4 : Succeeded
5 : Succeeded
6 : Succeeded
7 : ERROR: ... incremental snapshots ... limit ...

The disk had one snapshot from the lab plus six from the loop, which is the limit of seven incremental snapshots per disk every five minutes. The exact error text can vary, but it names the snapshot limit. Nothing is wrong with the disk.

Fix it: wait for the five-minute window, and design the schedule so it never needs more than seven snapshots per disk in five minutes:

Start-Sleep -Seconds 300
az snapshot create --resource-group rg-saat-l68 --name snap-l68-retry --source $diskId --incremental true --query provisioningState -o tsv
az snapshot list --resource-group rg-saat-l68 --query "length([?incremental])"

You should see Succeeded, and the count of incremental snapshots.

Real backup tools take one snapshot per disk per job. You hit this limit when several tools, or overlapping jobs, snapshot the same disk at the same time. Check that only one product owns each disk’s schedule.

2Break it: a report script that gets throttled

A monitoring script sends 30 Resource Graph queries in a burst:

C:\azure-lab\l68\Test-GraphBurst.ps1

You should see something like this:

OK: 15   Throttled (429): 15

Half the queries failed with 429. The script treats them as failures, so the report would be missing data. Retrying immediately would only make it worse, because the quota window hasn’t reset yet.

Fix it: use the retry function, which waits for the reset time the service gives:

. C:\azure-lab\l68\Invoke-GraphWithRetry.ps1
$ok = 0
foreach ($i in 1..30) { Invoke-GraphWithRetry -Query 'Resources | summarize count()' | Out-Null; $ok++ }
"OK: $ok"

You should see something like this:

429 received, waiting 4 s (try 1 of 6)
429 received, waiting 3 s (try 1 of 6)
OK: 30

Every query succeeded; the script just paused when told to. In a real report, the better fix is also to send fewer queries, for example one query that returns all subscriptions instead of one per subscription.

Resource Graph 429s fixed with a retry function
Same 30 queries, all successful once the script waits.

3Clean up

Delete the lab resource group, which removes the disk and all snapshots:

az group delete --name rg-saat-l68 --yes

✅ Checkpoint: You hit two real Azure limits on purpose and fixed both by slowing down the right way.

This happened at work

The backup window that kept failing at 1 am

A hospital protected 300 VMs with a third-party backup product that used Azure disk snapshots. The vendor’s default allowed 100 concurrent jobs. Every night at 1 am, about 20 jobs failed with throttling errors, always different VMs, and the retries at 3 am mostly worked.

All jobs started at the same minute, under one service principal, in one subscription. They hammered Resource Manager with snapshot, disk and VM calls at once, used up the write and read buckets, and got 429s. The vendor’s retry waited only 10 seconds, so it hit the same wall.

What they changed: concurrency down to 25 jobs, start times spread over two hours by protection group, longer backoff in the product settings, and a workbook that charts 429s from the Activity log and the backup product’s logs. Failed jobs dropped to zero, and the whole window finished earlier than before.

Common mistakes and troubleshooting

MistakeWhat you seeWhat to do
Retrying immediately on 429More 429s, longer outageWait for Retry-After or the reset header.
Many parallel workers, one identityThrottling at peak timesLimit concurrency; spread schedules.
Several tools snapshotting one diskSnapshot limit errorsOne owner per disk schedule.
One query per subscriptionGraph quota used fastQuery many subscriptions in one call.
Ignoring rate-limit headersThrottling comes as a surpriseLog the remaining-quota headers.
Treating 429 as an outageEscalations, rollbacksIt’s a pacing signal, not a failure of Azure.

Interview questions

Click a question to open it. Each one is written the way an interviewer would ask it. Under it you will see why they ask it and one way you could answer, in your own words.

Fresher level

1. What does HTTP 429 mean?

Why they ask this: The basic concept.

How you might answer: Too Many Requests: the service is limiting how fast you call it and you should wait before retrying.

2. What is Retry-After?

Why they ask this: Basic protocol.

How you might answer: A response header that says how many seconds to wait before the next request.

3. How many incremental snapshots can one disk have?

Why they ask this: A useful limit.

How you might answer: Up to 500 at any time, and up to seven new ones every five minutes.

2–5 years experience

4. A backup tool reports throttling every night. What do you check?

Why they ask this: A real troubleshooting path.

How you might answer: Concurrency and start times, the identity used, ARM 429s in logs, snapshot limits per disk, overlapping tools on the same disks, and the tool’s retry and backoff settings.

5. How do you write retry logic for Azure APIs?

Why they ask this: Engineering practice.

How you might answer: Retry only on throttling and transient codes, honour Retry-After or reset headers, use exponential backoff with jitter, cap the attempts, and log each retry.

6. How do you avoid Resource Graph throttling in reports?

Why they ask this: Practical depth.

How you might answer: Fewer, broader queries, paging with skip tokens, caching results, spacing queries over time, and reading x-ms-user-quota-remaining.

Senior level

7. How would you design automation that runs across 200 subscriptions?

Why they ask this: Scale thinking.

How you might answer: Batch reads with Resource Graph, queue writes with controlled concurrency, use several identities where appropriate, respect regional buckets, and monitor 429 rates as a health metric.

8. What is the difference between a quota and a throttling limit?

Why they ask this: Concepts.

How you might answer: A quota caps how much you can have (cores, snapshots per disk). A throttling limit caps how fast you can ask. Quotas need increases or design changes; throttling needs pacing.

9. How do you handle limits in multi-tool backup estates?

Why they ask this: Senior operations.

How you might answer: One tool per workload, staggered schedules, concurrency agreed with vendors, monitoring of 429s and snapshot errors, and tests at full scale before go-live.

Quick quiz

Q1. 429 means?

  1. Not found
  2. Too many requests
  3. Unauthorized
  4. Server down
Show answer

B — The service is limiting your rate.

Q2. Best response to a 429?

  1. Retry at once
  2. Wait for Retry-After, then retry
  3. Delete resources
  4. Open a support case
Show answer

B — Back off for the time given.

Q3. Incremental snapshots per disk every five minutes?

  1. 3
  2. 7
  3. 50
  4. 500
Show answer

B — Up to seven per five minutes.

Q4. Which header shows Resource Graph quota left?

  1. x-ms-request-id
  2. x-ms-user-quota-remaining
  3. Retry-After
  4. ETag
Show answer

B — It counts down within the window.

Q5. Quota vs throttling?

  1. Same thing
  2. Quota is how much; throttling is how fast
  3. Throttling is cost
  4. Quota is speed
Show answer

B — They need different fixes.

Quick recap

You can now recognise throttling, read the signals Azure gives, and pace scripts and tools so they finish reliably.

  • 429 means slow down, not outage.
  • Azure sends remaining-quota headers on every response.
  • Back off for Retry-After or the reset time, with jitter.
  • Disks allow seven incremental snapshots per five minutes.
  • Limit concurrency and spread schedules in backup tools.

Next lesson

Lesson 69: Azure Backup failures → You fix Azure Backup failures: VM agent problems, snapshot timeouts and restore errors.

Further reading: Azure throttling 429

Want to go deeper into Azure throttling 429? See Microsoft’s Understand how Azure Resource Manager throttles requests and Guidance for throttled requests in Azure Resource Graph. You can also see the full plan for this course on the Azure Cloud Administration course page.

Scroll to Top