I'm measuring how long it takes to start about 100 Azure VMs with the Azure CLI. Starting them in batches of 20 took roughly 30 minutes overall, while batches of 50 took about 46 minutes. These are total wall-clock times for all 100 VMs, not the duration of one batch.
The slower result with higher concurrency makes me wonder whether ARM or Compute throttling, backend queuing, or another platform limit is affecting VM power operations—even if no obvious 429 errors are returned.
I'm planning to rerun the test using asynchronous start requests and then poll each VM's power state until everything is running. Has anyone tested starting 100 or more Azure VMs at once? Is there a practical concurrency or batch-size recommendation, and can these operations be slowed by throttling without producing clear errors?
2 Answers
Before drawing conclusions about Azure throttling, check whether the commands use the CLI’s --no-wait option. Without it, each az vm start call waits for the operation to finish, so your script may be serializing work within each batch. Submit the starts asynchronously, then track the operations or poll the VM states separately. The achievable rate can also vary by region and subscription.
A large difference between concurrency levels could indicate control-plane queuing or rate limiting, but the exact VM start limits are not always easy to find and may change by service, region, and subscription. Throttling does not necessarily present as a straightforward 429; queued operations can also show up as longer completion times. I’d test several levels, such as 10, 20, 50, and 100, while recording request responses, rate-limit headers, and the time each VM actually reaches the running state. For a fleet that may grow to 250 VMs, asynchronous submission plus polling should give a clearer picture than waiting for each batch synchronously.
That’s what I’m planning to do next. I haven’t found clear public limits specifically for VM power operations, so I’ll compare several concurrency levels and inspect the response metadata while measuring the final ready time.

Good catch—I checked the script and it was missing --no-wait. I’ll rerun the measurements with asynchronous starts before comparing concurrency levels.