On this page
- Signing in and choosing a subscription
- Service principals, managed identity and workload identity
- RBAC: roles, scopes, and why Contributor is not enough
- Resource groups, deployments and what-if
- Quotas, SKUs and capacity
- Throttling and querying at scale
- Diagnosing a failed deployment
- AKS, ACR and Key Vault
- Output, queries and scripting
- Troubleshooting specific errors
- Production checklist
- Frequently asked questions
- Related resources
Almost every Azure problem an engineer actually hits falls into one of three buckets: it will not authenticate, it will not authorize, or it will not deploy. The portal hides which of the three you are in; the CLI tells you directly. This reference is organized around that — sign in, prove who you are, get the right role at the right scope, deploy, and read the error ARM gives back.
Signing in and choosing a subscription
The single most common cause of a confusing Azure error is being signed in to the wrong tenant or the wrong subscription. Check that before anything else.
Authentication & context
| Command | What it does | Risk |
|---|---|---|
az login | Interactive browser sign-in. | Safe |
az login --use-device-code | Sign in from a headless box, bastion or container. | Safe |
az login --tenant <tenantId> | Sign in against a SPECIFIC tenant — required for guest/multi-tenant accounts. | Safe |
az login --identity | Authenticate as the VM/container's managed identity. No secret involved. | Safe |
az login --service-principal -u <appId> -p <secret> --tenant <tenantId> | Non-interactive sign-in for automation. | Caution |
az account show -o table | Who am I, in which subscription and tenant. Run this FIRST when confused. | Safe |
az account list --all -o table | Every subscription the account can see, across tenants. | Safe |
az account set --subscription <name-or-id> | Change the active subscription for subsequent commands. | Caution |
az account get-access-token --query expiresOn | Confirm you hold a live token and when it dies. | Safe |
az logout | Clear the local token cache — the fix for a stale or wrong-tenant session. | Safe |
No commands match that filter.
# The three-line orientation check. Almost every "it works locally" bug is here.
az account show --query "{user:user.name, sub:name, tenant:tenantId}" -o yaml
az account list --all --query "[].{name:name, id:id, tenant:tenantId}" -o table
az account set --subscription "prod-platform"
Service principals, managed identity and workload identity
Three ways for code to authenticate, in increasing order of how much you should prefer them.
Machine identity
| Command | What it does | Risk |
|---|---|---|
az ad sp create-for-rbac --name <n> --role Reader --scopes <scope> | Create a service principal AND a role assignment. Prints the secret ONCE. | Caution |
az ad sp list --display-name <n> -o table | Find an existing service principal. | Safe |
az ad app credential reset --id <appId> | Rotate a client secret. Invalidates the old one immediately. | Destructive |
az ad app federated-credential create --id <appId> --parameters creds.json | Passwordless OIDC trust — GitHub Actions with no stored secret. | Caution |
az identity create -g <rg> -n <name> | Create a user-assigned managed identity. | Caution |
az vm identity assign -g <rg> -n <vm> | Give a VM a system-assigned managed identity. | Caution |
az ad sp show --id <appId> --query appRoles | Inspect what an app registration actually exposes. | Safe |
No commands match that filter.
For SDK-based apps, DefaultAzureCredential walks a chain of these — environment variables, then workload identity, then managed identity, then the developer’s az login session. That is convenient locally and a frequent source of confusion in production, because it can silently succeed as the wrong identity:
# What the SDK will pick up from the environment
env | grep -E '^AZURE_(CLIENT_ID|TENANT_ID|CLIENT_SECRET|FEDERATED)'
# Prove which identity is actually in play before blaming the code
az account show --query "{name:user.name, type:user.type}" -o yaml
When it fails, the message names every credential it tried — read the chain, not just the last line. Full walkthrough: DefaultAzureCredential failed.
RBAC: roles, scopes, and why Contributor is not enough
Authorization failures are the second bucket, and they hinge on two things people conflate: the scope a role is assigned at, and whether the operation is management plane or data plane.
Roles & assignments
| Command | What it does | Risk |
|---|---|---|
az role assignment list --assignee <id> --all -o table | Every assignment a principal holds, at every scope. | Safe |
az role assignment list --scope <scope> --include-inherited -o table | Who has access HERE, including inherited from above. | Safe |
az role assignment create --assignee <id> --role <role> --scope <scope> | Grant a role. Needs Owner or User Access Administrator. | Caution |
az role assignment delete --assignee <id> --role <role> --scope <scope> | Revoke. Takes effect within minutes, not instantly. | Destructive |
az role definition list --name <role> --query '[].permissions' | What a role actually permits — read this before assigning it. | Safe |
az provider operation show --namespace Microsoft.Storage | Every operation a provider defines, for building a custom role. | Safe |
az lock list --resource-group <rg> -o table | Locks block deletes/changes regardless of RBAC. Check when a delete 'silently' fails. | Safe |
No commands match that filter.
Scope is hierarchical and inherits downward:
management group → subscription → resource group → individual resource
An assignment at a resource group covers everything in it; one at a resource covers only that resource. Grant at the narrowest scope that works.
That distinction maps directly onto the errors:
- AuthorizationFailed — management plane. The principal has no role granting that operation at that scope.
- AuthorizationPermissionMismatch — data plane. Usually
Contributorwhere a Data role was required. - ServerFailedToAuthenticateRequest — the storage request never presented a valid credential; often a clock skew or a malformed SAS.
- LinkedAuthorizationFailed — you have rights on the resource but not on something it references, like a subnet or a managed identity.
- PrincipalNotFound — a freshly created service principal has not replicated yet. Retry with backoff; it is a timing problem, not a permissions one.
Resource groups, deployments and what-if
Resources & deployments
| Command | What it does | Risk |
|---|---|---|
az group create -n <rg> -l <region> | Create a resource group. | Caution |
az group delete -n <rg> --yes --no-wait | Deletes EVERYTHING inside it. There is no undo. | Destructive |
az resource list -g <rg> -o table | Everything in a group, with type and location. | Safe |
az deployment group what-if -g <rg> -f main.bicep | The plan step. Shows create/modify/delete BEFORE you apply. | Safe |
az deployment group create -g <rg> -f main.bicep -p @params.json | Apply a template to a resource group. | Destructive |
az deployment group create --mode Complete ... | Complete mode DELETES resources not in the template. Rarely what you want. | Destructive |
az deployment group list -g <rg> --query '[].{n:name,state:properties.provisioningState}' -o table | Deployment history and outcomes. | Safe |
az deployment group show -g <rg> -n <dep> --query properties.error | The REAL error, nested inside the deployment record. | Safe |
az provider register --namespace Microsoft.<Service> | Register a resource provider on the subscription. One-off, takes minutes. | Caution |
az provider list --query "[?registrationState=='NotRegistered'].namespace" -o tsv | Providers not yet registered — a common first-deploy blocker. | Safe |
No commands match that filter.
The what-if output is the closest Azure gets to terraform plan, and it is free:
az deployment group what-if -g rg-prod -f main.bicep -p @prod.params.json
# Symbols: + create ~ modify - delete = no change
Two provider-registration errors block a great many first deployments — MissingSubscriptionRegistration and NoRegisteredProviderFound. Both are fixed by az provider register, and both take a few minutes to propagate.
Quotas, SKUs and capacity
Three different failures look identical from a distance — “the VM would not create” — and each has a different fix.
# 1. QUOTA: am I allowed this many cores in this region?
az vm list-usage --location eastus \
--query "[?contains(name.value,'cores')].{name:localName, used:currentValue, limit:limit}" -o table
# 2. SKU AVAILABILITY: is this size even offered here, and is it restricted?
az vm list-skus --location eastus --size Standard_D4s --all \
--query "[].{name:name, zones:locationInfo[0].zones, restriction:restrictions[0].reasonCode}" -o table
# 3. CAPACITY: the size exists and I have quota, but the region/zone is full right now.
Which error means which
| Command | What it does | Risk |
|---|---|---|
QuotaExceeded / OperationNotAllowed | A subscription LIMIT. Request an increase — capacity is not the problem. | Safe |
SkuNotAvailable | That size is not offered in the region/zone, or is restricted for your subscription. | Safe |
AllocationFailed | Transient CAPACITY. The cluster is full; try another zone, size family or region. | Safe |
No commands match that filter.
The distinction matters because the responses are completely different: a quota problem needs a support request, a SKU problem needs a different size or region, and an allocation failure often just needs a retry in another zone. Details in SkuNotAvailable, AllocationFailed, QuotaExceeded and vCPU quota.
Networking has its own quiet limits — PublicIpCountLimitReached and SubnetIsFull — which surface as deployment failures rather than as quota messages.
Throttling and querying at scale
Azure Resource Manager rate-limits per subscription. A loop of az calls across hundreds of resources will hit it.
# ARM tells you how much budget is left, in a response header
az group list --debug 2>&1 | grep -i 'x-ms-ratelimit-remaining-subscription-reads'
When you get a 429, read Retry-After and honour it — retrying immediately makes it worse. Then remove the loop entirely: Azure Resource Graph answers across every subscription in one server-side query, which is both faster and far cheaper against the rate limit.
# Instead of iterating subscriptions and resource groups:
az graph query -q "
Resources
| where type =~ 'microsoft.compute/virtualmachines'
| project name, resourceGroup, location, properties.hardwareProfile.vmSize
| order by name asc
" -o table
See 429 TooManyRequests for the backoff strategy.
Diagnosing a failed deployment
The CLI’s top-level error is frequently a summary. The useful detail is nested.
# 1. The actual error object from the deployment record
az deployment group show -g rg-prod -n main \
--query "properties.error" -o json
# 2. Failed operations within that deployment, with per-resource status
az deployment operation group list -g rg-prod -n main \
--query "[?properties.provisioningState=='Failed'].{res:properties.targetResource.resourceName, msg:properties.statusMessage}" -o json
# 3. The activity log — what the platform recorded, including who did it
az monitor activity-log list -g rg-prod --offset 1h \
--query "[?level=='Error'].{op:operationName.localizedValue, status:status.value, caller:caller}" -o table
# 4. When all else fails, see the raw request and response
az deployment group create -g rg-prod -f main.bicep --debug
Common deployment blockers
| Command | What it does | Risk |
|---|---|---|
InvalidTemplateDeployment | Policy or a validation rule rejected it. The nested message names which. | Safe |
RequestDisallowedByPolicy | Azure Policy blocked it. Read the policy definition ID in the error. | Safe |
ResourceGroupBeingDeleted | A delete is still in flight. Wait — you cannot create into it. | Safe |
Conflict / another operation in progress | Concurrent writes to one resource. Serialise, then retry. | Safe |
InvalidApiVersionParameter | The template's apiVersion is not valid for that provider. | Safe |
ScopeLocked | A CanNotDelete or ReadOnly lock is in force. Locks beat RBAC. | Safe |
No commands match that filter.
RequestDisallowedByPolicy deserves a note: it means the deployment was valid and Azure Policy refused it anyway. The error carries the policy assignment ID — look that up rather than editing the template blindly, because the policy usually exists for a reason.
AKS, ACR and Key Vault
The three services whose failures most often turn out to be identity problems wearing a different hat.
AKS, ACR & Key Vault
| Command | What it does | Risk |
|---|---|---|
az aks get-credentials -g <rg> -n <cluster> | Merge cluster credentials into kubeconfig. | Caution |
az aks get-credentials ... --overwrite-existing | Replace a stale entry — the fix for confusing kubectl auth errors. | Caution |
az aks nodepool list -g <rg> --cluster-name <c> -o table | Node pools, sizes, counts and provisioning state. | Safe |
az aks update -g <rg> -n <c> --attach-acr <acrName> | Grant the cluster AcrPull. The proper fix for ImagePullBackOff from ACR. | Caution |
az acr login -n <registry> | Authenticate Docker to ACR using your Azure identity. | Safe |
az acr repository list -n <registry> -o table | What is actually in the registry. | Safe |
az keyvault secret show --vault-name <v> -n <secret> | Read a secret — needs data-plane permission, not just Contributor. | Caution |
az keyvault show -n <v> --query properties.enableRbacAuthorization | Tells you WHICH permission model this vault uses. | Safe |
No commands match that filter.
The AKS failures that look like Kubernetes problems but are not: ImagePullBackOff from ACR is usually a missing AcrPull assignment — --attach-acr fixes it properly — and ACR unauthorized is an expired az acr login. Genuine cluster-side issues are covered by NodeNotReady, insufficient resources and CrashLoopBackOff.
Output, queries and scripting
Making az scriptable
| Command | What it does | Risk |
|---|---|---|
-o table | Human-readable. Never parse it. | Safe |
-o tsv | The one to use in shell scripts — no quotes, no JSON. | Safe |
--query "[].{n:name, l:location}" | JMESPath projection. Applied client-side, after download. | Safe |
--only-show-errors | Suppress warnings so CI logs stay readable. | Safe |
--no-wait | Return immediately instead of polling a long operation. | Caution |
az <cmd> --debug | Full request/response, including the ARM error body. | Safe |
az config set core.output=table | Set a default output format persistently. | Safe |
az extension add --name <ext> | Install an extension (aks-preview, resource-graph, …). | Caution |
az upgrade | Update the CLI and its extensions. | Caution |
No commands match that filter.
# A safe scripting shape: fail fast, no interactive prompts, machine-readable output.
set -euo pipefail
az config set core.only_show_errors=true --only-show-errors
vm_ids=$(az vm list -g rg-prod --query "[].id" -o tsv)
for id in $vm_ids; do
az vm show --ids "$id" --query "{name:name, size:hardwareProfile.vmSize}" -o tsv
done
That loop is fine for a handful of VMs and wrong for a thousand — at that point it is one az graph query, for the throttling reasons above.
Troubleshooting specific errors
The Azure failures engineers hit most often, each with a dedicated guide:
- AADSTS700016 & AADSTS50076 — app not found in the tenant, and MFA required.
- AADSTS50105 — the user is not assigned to the application.
- AADSTS7000215 — invalid client secret, very often the secret ID instead of its value.
- AADSTS7000222 — the client secret expired.
- AADSTS90002 — tenant not found.
- 429 TooManyRequests — ARM throttling; honour
Retry-Afterand batch. - SkuNotAvailable — the size is not offered in that region or zone.
- AllocationFailed — transient capacity; try another zone or size.
- AuthorizationPermissionMismatch — a data-plane role was needed, not
Contributor. - DefaultAzureCredential failed — read the whole credential chain, not the last line.
- RequestDisallowedByPolicy — Azure Policy refused a valid deployment.
- Key Vault Forbidden — the vault’s permission model, not the role.
For a code not listed here, the Entra sign-in error hub carries the full AADSTS lookup table, or paste the message into the Incident Assistant.
Production checklist
az account showbefore anything surprising. Wrong tenant and wrong subscription explain more failures than any bug.- Managed identity or workload identity over client secrets. A secret you never create cannot expire in production.
- Assign roles at the narrowest scope that works, and prefer a group over a person.
- Know whether the operation is management plane or data plane before granting
Contributoragain. what-ifbefore every deployment, and read the delete list — especially with--mode Complete.- Register resource providers as part of subscription bootstrap, not during the first deploy.
- Honour
Retry-Afteron 429, and replace inventory loops withaz graph query. - Check
enableRbacAuthorizationbefore debugging a Key Vault permission. - Read
properties.errorfrom the deployment, not just the CLI’s summary line. - Rotate and inventory client secrets you cannot yet remove — expiry is a scheduled outage.
Frequently asked questions
Why does my service principal get AuthorizationFailed when it has Contributor?
Two usual causes. Either the assignment is at a narrower scope than the operation needs — Contributor on one resource group does not let you write to another — or the operation is data plane rather than management plane. Contributor on a storage account manages the account itself and grants no access to the blobs inside it; that needs a role like Storage Blob Data Contributor. Run az role assignment list --assignee <id> --all and compare the scope against what you are actually calling.
What is the difference between a service principal and a managed identity?
A service principal is an identity you create and hold a credential for — a client secret or certificate — which means you own the rotation, storage and leak risk. A managed identity is issued and rotated by Azure for a specific resource, with no secret you ever see or handle. If the workload runs inside Azure, use a managed identity. For CI running outside Azure, workload identity federation gives you the same secretless model by trusting the pipeline’s OIDC token.
How do I tell whether a VM failure is quota, SKU or capacity?
They are three different problems. QuotaExceeded and OperationNotAllowed mean a subscription limit — check az vm list-usage and request an increase. SkuNotAvailable means that size is not offered in that region or zone, or is restricted for your subscription — check az vm list-skus --all and look at the restriction reason. AllocationFailed means the size exists and you have quota, but the cluster is full right now — retry in another zone, a different size family, or another region.
Why does az work locally but fail in my pipeline?
Because they are different identities. Locally you are a user with your own role assignments; in CI you are a service principal or federated identity that usually has far fewer. Print the identity in the pipeline with az account show and list its assignments with az role assignment list --assignee <id> --all. The second most common cause is tenant: an account with guest access in several tenants can log in to the wrong one unless --tenant is explicit.
What does —mode Complete actually delete?
Every resource in the target resource group that your template does not declare. It is intended for resource groups fully owned by one template, and it is dangerous anywhere a person or another pipeline has also created something. Always run az deployment group what-if first and read the entries marked with -. Incremental mode, the default, never deletes.
How do I stop hitting 429 from ARM?
Stop iterating. Most throttling comes from scripts that loop over subscriptions or resource groups calling az resource list, which is one request each. az graph query answers the same question across every subscription in a single server-side query. Where you genuinely must loop, honour the Retry-After header, back off exponentially, and remember that --query filters after the download — it reduces what you see, not what you requested.
Related resources
- Guide: Cloud Security — the identity and least-privilege principles underneath the RBAC section here.
- Guide: Kubernetes Security — where AKS workloads pick up from cluster-side controls.
- Troubleshooting hub: Entra ID sign-in errors — the full AADSTS code reference.
- Tool: Incident Assistant — paste an Azure error and get an ordered triage plan.
Did this solve your problem?
That looks like it may contain a secret (key, token, password, or connection string). Please remove it — a note with a detected secret can’t be published.
Thanks — that helps. Published notes appear after a quick review.
Continue learning
Related Core Guides that build on this one.
- gcloud CLIA gcloud CLI reference for engineers who operate real Google Cloud projects — the two separate credentials, enabling APIs, IAM inheritance and the actAs trap, org policy constraints that beat Owner, quotas versus capacity, GKE auth, and etag preconditions.
- AWS CLIAn AWS CLI reference for engineers who operate real accounts — the credential resolution chain, IAM policy evaluation, S3 permission quirks, service quotas and throttling, CloudFormation rollback states, waiters and querying.
- Cloud SecurityCloud security for DevOps — the shared responsibility model, IAM and least privilege, secrets, encryption, network segmentation, and supply-chain defense.
- Kubernetes SecurityHarden Kubernetes for production — RBAC, Pod Security Standards, NetworkPolicy, admission control and runtime security, with secure vs. insecure YAML side by side.
- System DesignSystem design for engineers who operate what they build — scalability, availability, data, queues and failure modes, framed around real production architecture.