Someone runs az group delete against the wrong resource group at 4pm on a Friday, confirms the prompt out of muscle memory, and a production database server is gone. Or a well-meaning engineer “cleans up” a subscription, sees a virtual network with a confusing name, and deletes it — taking three apps offline. These are not exotic failures; they are the single most common way teams lose production resources in Azure, and almost none involve malice — just a tired human, an ambiguous name, and one click too many. Azure resource locks exist precisely for this: a thin, free guardrail that makes “delete” or “modify” fail loudly instead of succeeding silently.
A resource lock is a management-plane setting you attach to a subscription, resource group, or individual resource that blocks certain operations regardless of the caller’s access. There are two kinds: a CanNotDelete lock lets people read and modify a resource but blocks deletion, and a ReadOnly lock goes further, blocking deletion and most modifications so the resource is effectively frozen. The crucial detail — what makes locks different from permissions — is that a lock applies to everyone, including subscription Owners and Global Administrators; even the person who created it is stopped until they remove it first. That is the whole point: locks are not about who you are, they force a deliberate, two-step action before something irreversible happens.
This article is a step-by-step implementation guide. You will learn when to reach for a lock versus an Azure Policy or an RBAC role, where in the hierarchy to place one, and how locks inherit — then create, inspect, and remove them three ways (Azure portal, az CLI, and Bicep in source control) with exact commands, expected output, validation, and a clean teardown. We also walk the gotchas that bite real teams: locks that break a legitimate deployment, and the storage-key edge case where a ReadOnly lock does more than you expected.
What problem this solves
Without locks, the only thing between a production resource and accidental deletion is RBAC — a blunt instrument for this job. The roles that let people do their daily work (Contributor, Owner) also let them delete anything in scope; you cannot say “this person can manage the database but must never delete this specific server” with roles alone. So teams either over-grant (and live with the risk) or under-grant (and create a ticket queue). Locks cut the knot: grant the access people need, then lock the handful of resources whose deletion would be catastrophic.
The failure locks prevent is specific and expensive. A deleted resource group takes every resource inside it, in dependency order, with no recycle bin for most resource types. A deleted Key Vault may be recoverable via soft-delete, but a deleted App Service plan, a Log Analytics workspace past retention, or a Cosmos DB account without continuous backup is simply gone — and the cost is the outage and lost data, not just the rebuild. A lock turns all of that into a clear error: “The resource cannot be deleted because it is locked.”
Who hits this: every team that runs anything important in Azure with more than one person on the subscription. It bites hardest on shared subscriptions where dev, test, and prod live side by side; on resource groups holding stateful infrastructure (databases, storage, key vaults, networking); and on “landing zone” resources — hub VNets, firewalls, DNS zones — that many workloads silently depend on. If a single accidental delete would ruin someone’s week, the resource it would hit should be locked.
Learning objectives
By the end of this article you can:
- Explain the difference between a CanNotDelete lock and a ReadOnly lock, and what each one blocks and allows.
- Decide where to place a lock in the subscription → resource group → resource hierarchy and predict how it inherits to children.
- Choose correctly between a resource lock, an Azure Policy, and an RBAC role for a given protection goal.
- Create, list, and delete locks in the Azure portal, with the
az lockCLI, and declaratively in Bicep. - Validate that a lock is working by attempting a blocked operation and reading the exact error it returns.
- Recognise and avoid the common ways locks break legitimate work — failed deployments, child-resource operation failures, and the storage-key and backup edge cases.
- Manage the permission needed to administer locks (
Microsoft.Authorization/locks/*) without handing out broad Owner access.
Prerequisites & where this fits
You should be comfortable with the basic Azure object model: a subscription contains resource groups, and a resource group contains resources (a VM, a storage account, a SQL server). If that hierarchy is fuzzy, read Azure Resource Hierarchy Explained first — locks live at exactly those three scopes. You need a subscription to experiment in (the lab uses only near-zero-cost resources), the Azure CLI or Cloud Shell, and an account with at least Owner on a resource group so you can create resources and manage locks.
Locks are one of three governance guardrails, and the most basic. RBAC controls who can do what; Azure Policy controls what configurations are allowed and can audit or deny non-compliant resources at scale (see Azure Policy for Governance at Scale); resource locks control whether a specific resource can be deleted or changed at all, by anyone. In a mature setup all three combine: Policy defines the rules, RBAC grants the access, locks protect the crown jewels from the people who legitimately hold it. Locks are deliberately simple — two options, three scopes — which is why every team should use them from day one.
Core concepts
A handful of ideas make every later decision obvious.
A lock is a management-plane object, not a permission. It lives in the Azure Resource Manager (ARM) control plane as a child of whatever you lock, and intercepts delete and write operations before they reach the resource — independent of the caller’s RBAC. This is why an Owner is blocked: ownership grants permission to call the API, but the lock rejects the operation anyway.
There are exactly two lock levels. CanNotDelete (shown in the portal as Delete) blocks deletion only. ReadOnly (shown as Read-only) blocks deletion and all create/update operations on the locked scope. There is no “no-read” lock and no per-operation custom lock — those two levels are the entire vocabulary. For finer control than “can’t delete” or “can’t change,” reach for RBAC or Policy, not a lock.
Locks inherit downward, and the most restrictive wins. A subscription lock applies to every resource group and resource under it; a group lock applies to every resource in it. When multiple locks apply — say CanNotDelete on the group and ReadOnly directly on the resource — the most restrictive one is enforced. You cannot “unlock” a child with a weaker lock; inheritance only ever adds restriction.
Management-plane, not data-plane. A lock stops you from deleting the storage account (management plane). It does not, by itself, stop someone from deleting a blob inside it (data plane) — that is governed by data-plane access. The one consequential exception: a ReadOnly lock can block management-plane operations that some data-plane tools secretly rely on, like listing storage account keys (more on that later). The rule to hold: locks protect the resource as an ARM object, not necessarily the data inside it.
Removing a lock is itself a privileged, deliberate act. To delete or modify a locked resource you must first remove the lock, which requires the Microsoft.Authorization/locks/* permission — held by Owner and User Access Administrator by default. That two-step shape (remove the lock, then perform the operation) is the safety mechanism: it converts a fat-fingered command into a conscious decision. (Every term above is defined again for lookup in the Glossary at the end.)
CanNotDelete vs ReadOnly: what each one actually blocks
The difference between the two levels is the difference between “a useful default” and “a sledgehammer you’ll regret.”
CanNotDelete is the lock you will use 90% of the time. It blocks any delete operation against the locked scope but lets everything else through — people can read, reconfigure, restart, re-tag, and deploy into the resource; they simply cannot delete it (or, for a group-level lock, delete the group or anything inside it). This is almost always what you want: keep the resource operable while removing the one irreversible action.
ReadOnly blocks deletion and all create/update operations, freezing the resource at its current configuration. This sounds attractive for “lock down prod completely,” but it is a much bigger hammer than its name suggests, because many routine operations are writes under the hood — restarting a VM, scaling a plan, rotating a certificate, even listing keys all fail. ReadOnly is right for a genuinely static resource (a reference DNS zone, a finalised network config), but it is the wrong default for anything a team operates day to day.
Here is the side-by-side that settles which to use:
| Operation | CanNotDelete | ReadOnly |
|---|---|---|
| Read the resource / view properties | Allowed | Allowed |
| Delete the resource | Blocked | Blocked |
| Update configuration (resize, settings) | Allowed | Blocked |
| Restart / start / stop (where it’s a write) | Allowed | Blocked |
| Add or change tags | Allowed | Blocked |
| Deploy a new child resource into the scope | Allowed | Blocked |
| List storage account keys (a POST) | Allowed | Blocked (key listing is a write) |
| Move the resource to another group | Blocked | Blocked |
The decision rule in one line: use CanNotDelete unless you have a specific reason the resource must never change either — then, and only then, use ReadOnly, and test it against your real operational workflow first. For “prevent only some changes,” neither level fits; that is an Azure Policy or RBAC job.
Where to put a lock: scope and inheritance
A lock can sit at exactly three scopes, and choosing the scope is choosing the blast radius. Because locks inherit downward, the question is always “what is the smallest scope that covers everything I want protected, without freezing things I need to keep flexible?”
Subscription scope locks everything — every resource group and resource, current and future. Rare and heavy: occasionally right for a subscription holding nothing but locked-down shared infrastructure, but on a normal subscription it blocks routine work everywhere. Use it only with eyes open.
Resource-group scope is the workhorse. A CanNotDelete lock on a group means nobody can delete the group or anything in it, while teams keep operating the resources normally. For a group holding a workload’s stateful pieces — database, storage, key vault, networking — one group-level lock protects them all and automatically covers any resource added later. Usually the best ratio of protection to effort.
Resource scope is surgical: lock one VM or storage account and leave the rest of the group free. Use it when only specific resources are precious and the group also holds disposable things you recreate routinely.
The inheritance and precedence rules, made concrete:
| Lock placed on… | What it protects | Covers new children added later? | Typical use |
|---|---|---|---|
| Subscription | Every RG and resource under it | Yes | Locked shared-infra subscriptions only |
| Resource group | The group + every resource in it | Yes | Protecting a whole workload’s stateful infra |
| Resource | Just that one resource | N/A | Surgically protecting the precious few |
When more than one lock applies, the most restrictive level wins, and you cannot weaken an inherited lock from below:
| Scenario | Effective result |
|---|---|
CanNotDelete on RG, nothing on resource |
Resource: cannot delete |
CanNotDelete on RG, ReadOnly on resource |
Resource: read-only (most restrictive wins) |
ReadOnly on RG, CanNotDelete on resource |
Resource: read-only (inherited, can’t weaken) |
Two CanNotDelete (RG + resource) |
Resource: cannot delete (both agree) |
CanNotDelete on subscription |
Every resource everywhere: cannot delete |
The practical takeaway: put the lock at the highest scope where everything beneath it deserves the same protection. “All precious” group → lock the group. “Mostly disposable with a few crown jewels” → lock the jewels. Never lock the subscription unless it is genuinely all-or-nothing.
Lock vs Policy vs RBAC: choosing the right guardrail
The most common mistake with locks is using them for a job another tool does better. All three guardrails overlap in “stop bad things,” but each answers a different question.
| Guardrail | Answers the question | Granularity | Applies to | Bypassable by Owner? |
|---|---|---|---|---|
| RBAC | Who can perform which operations? | Per-action, per-identity | Identities (users, groups, service principals) | N/A — it is the permission |
| Azure Policy | What configurations are allowed? | Per-property, per-condition, at scale | Resource configurations | Depends on policy effect (deny vs audit) |
| Resource lock | Can this resource be deleted/changed at all? | All-or-nothing per level | A specific scope | No — blocks everyone until removed |
The clean way to think about it: reach for RBAC when the goal is “this person shouldn’t do this”; for Azure Policy when it is “no resource here should be configured this way” (region, encryption, tags — Policy evaluates configuration at scale, which locks cannot); and for a resource lock when it is “nobody, no matter who, should accidentally delete or change this specific thing.” The lock is the only one that stops an Owner.
A decision table for the common cases:
| If you want to… | Use | Not |
|---|---|---|
| Stop anyone from deleting the prod database | Resource lock (CanNotDelete) | RBAC (Owners can still delete) |
| Stop junior engineers from deleting anything | RBAC (Reader/limited role) | A lock (blocks seniors too) |
| Forbid creating resources outside India | Azure Policy (allowed locations) | A lock (can’t evaluate location) |
| Require all resources to carry a cost tag | Azure Policy (audit/deny) | A lock |
| Freeze a finalised network config entirely | Resource lock (ReadOnly) | Policy (won’t block every write) |
Locks and Policy even integrate: an Azure Policy with the DeployIfNotExists effect can auto-place a CanNotDelete lock on every resource of a given type, so “every production SQL server is locked” self-enforces rather than relying on a checklist. That is the advanced pattern; the manual lock is where you start.
Architecture at a glance
There is no diagram for this topic because the model is small enough to hold in your head — and holding it there is exactly the skill that prevents mistakes. Picture the Azure management plane as a single front door every delete and modify request passes through: the portal, the CLI, an ARM/Bicep deployment, and a Terraform apply all call the same Azure Resource Manager API. A resource lock is a gate bolted just inside that door, in front of the resource: when a delete request for a locked resource arrives, ARM checks for the lock before the resource and rejects the operation — even for a subscription Owner. An inherited lock places that same gate in front of every child of its scope, so a CanNotDelete lock on a resource group is one gate every resource in the group sits behind, and because inheritance only adds gates, the most restrictive one decides. RBAC is a different check at the same door — “is this caller allowed?” — independent of the lock’s “is this operation permitted here at all?”, and a request must pass both. That independence is why a lock stops the very Owners who configured it.
Real-world scenario
Northwind Logistics runs a fleet-tracking platform on Azure: a single shared subscription in Central India, with separate resource groups for rg-prod, rg-staging, and rg-shared (a hub VNet, a Private DNS zone, and a Key Vault every workload references). The platform team is three engineers, and like most small teams everyone holds Contributor on the subscription because splitting permissions felt like overhead. Monthly spend is around ₹2,40,000.
The incident was textbook. A contractor offboarding test resources worked from a runbook that said “delete the rg-staging-old group.” The subscription had both rg-staging and rg-staging-old; portal autocomplete surfaced rg-staging first, the contractor selected it, re-typed the name into the confirmation box, and confirmed the wrong one. The staging environment — a SQL database with two weeks of integration-test data and a hand-configured Application Gateway — was gone in ninety seconds. Rebuilding cost three days, and the near-miss was obvious: the same mistake against rg-prod would have been a company-ending outage.
The fix was deliberately minimal, because elaborate permission schemes get reverted under pressure. They placed one CanNotDelete lock on each of rg-prod and rg-shared at group scope, noted “Remove only via change ticket.” They deliberately left rg-staging unlocked so the team could keep rebuilding it freely, and dropped ReadOnly entirely after testing showed it blocked App Service scaling and certificate rotation. Total time: under ten minutes, captured in Bicep so the locks survive any redeploy.
Six months on: two more “delete the wrong thing” attempts happened — both caught by the lock, both surfacing “The scope cannot be deleted because it has a delete lock” in the logs, both turning into a thirty-second “oh, that’s locked” instead of an outage. The one real friction was a legitimate hub-VNet redeployment that failed against the group lock; the engineer removed the lock, deployed, and re-added it. The team decided that friction was a feature — it forced a conscious “yes, I really am changing shared infrastructure” — and put the remove-deploy-relock dance in the runbook. The lesson on the wall: “A lock is cheap insurance against the one mistake you can’t undo. Removing it is the premium.”
Advantages and disadvantages
Locks are a small feature with a sharp trade-off profile. Weigh it honestly.
| Advantages | Disadvantages |
|---|---|
| Free — no cost to create or hold a lock | Blunt: only two levels, all-or-nothing per level |
| Stops everyone, including Owners and Global Admins | Stops everyone — including legitimate automation and deploys |
| Trivial to apply (one setting, three scopes) | Inheritance can block child operations in non-obvious ways |
| Inherits automatically to current and future children | ReadOnly silently breaks many “read” tools (key listing, etc.) |
| Survives in IaC (Bicep/Terraform) for repeatable protection | Must be removed before any legitimate delete/modify, adding friction |
| Independent of RBAC — a second, orthogonal safety net | Does not protect data-plane objects (blobs, rows, messages) |
| Clear, specific error message names the lock as the cause | Easy to forget a lock exists until a deploy mysteriously fails |
The advantages dominate for stateful, hard-to-rebuild, widely-depended-on resources — databases, key vaults, hub networking, DNS zones — where accidental loss dwarfs the friction of occasionally removing a lock. The disadvantages dominate for disposable, frequently-redeployed resources — dev VMs, scratch storage, anything CI/CD recreates each run — where a lock just breaks the pipeline. The skill is matching tool to resource: lock the crown jewels, leave the scaffolding free, and never reach for ReadOnly without testing it against real operations first.
Hands-on lab
This is the centerpiece. You will create a near-free storage account, protect it three ways — portal, CLI, and Bicep — prove each lock works by attempting a blocked operation, and tear everything down cleanly. An empty storage account accrues a negligible amount and you delete it at the end.
Prerequisites:
- A subscription where you hold Owner (or a role with
Microsoft.Authorization/locks/*plus permission to create a resource group and storage account). - The Azure CLI (
az) installed, or Cloud Shell (Bash) — it hasazand Bicep preinstalled. - About 20 minutes.
Confirm you are signed in and on the right subscription:
az account show --query "{name:name, id:id, user:user.name}" -o table
# If it shows the wrong subscription:
# az account set --subscription "<your-subscription-name-or-id>"
Expected output: one row showing your subscription name, its GUID, and your identity. If this errors, run az login first.
Part A — Set up the playground (CLI)
Step 1 — Define variables and create a resource group. Variables keep the rest copy-pasteable.
RG=rg-lock-lab
LOC=centralindia
STG=stglocklab$RANDOM # storage names are global, lowercase/numbers only, 3-24 chars
az group create -n $RG -l $LOC -o table
Expected: a row with Name = rg-lock-lab, ProvisioningState = Succeeded.
Step 2 — Create a cheap storage account to protect. Standard LRS is the lowest-cost tier; empty, it costs effectively nothing.
az storage account create -n $STG -g $RG -l $LOC --sku Standard_LRS -o table
echo "Created: $STG"
Expected: a row ending in ProvisioningState = Succeeded, then the echoed name — note it, you will reuse it.
Part B — Lock with the Azure CLI
Step 3 — Create a CanNotDelete lock on the storage account. az lock create attaches a lock to a resource; you give it the resource’s name, type, and group.
az lock create \
--name lock-stg-no-delete \
--lock-type CanNotDelete \
--resource-group $RG \
--resource $STG \
--resource-type Microsoft.Storage/storageAccounts \
--notes "Lab: prevent accidental deletion of the storage account"
Expected output: a JSON object describing the lock, including "level": "CanNotDelete" and a "name": "lock-stg-no-delete". The lock now exists.
Step 4 — Verify the lock is in place. List locks scoped to the resource:
az lock list --resource-group $RG --resource $STG \
--resource-type Microsoft.Storage/storageAccounts \
--query "[].{name:name, level:level, notes:notes}" -o table
Expected output: one row showing lock-stg-no-delete, CanNotDelete, and your note.
Step 5 — Prove it works: attempt to delete the locked resource. This should fail — that is the success condition of the lab.
az storage account delete -n $STG -g $RG --yes
Expected output: an error, not a deletion. The message reads something like:
(ScopeLocked) The scope '/subscriptions/.../resourceGroups/rg-lock-lab/providers/
Microsoft.Storage/storageAccounts/stglocklab12345' cannot perform delete operation
because following scope(s) are locked: '/subscriptions/.../storageAccounts/stglocklab12345'.
Please remove the lock and try again.
Seeing ScopeLocked and “cannot perform delete operation … locked” is the proof the guardrail works. The storage account is untouched.
Step 6 — Remove the CLI lock so the resource is clean for the portal steps:
az lock delete --name lock-stg-no-delete -g $RG \
--resource $STG --resource-type Microsoft.Storage/storageAccounts
(To see ReadOnly instead, recreate the lock with --lock-type ReadOnly and run az storage account update -n $STG -g $RG --set tags.env=lab — it fails on a modify, not a delete, because ReadOnly blocks writes too.)
Part C — Lock in the Azure portal
Now the same thing through the UI, so you recognise it during an incident without a terminal.
Step 7 — Navigate to the resource’s Locks blade.
- Open the Azure portal and go to Resource groups →
rg-lock-lab. - Click your storage account (
stglocklab…). - In the left menu, under Settings, select Locks.
Expected screen: a Locks pane, currently empty (you removed the CLI locks in Step 6).
Step 8 — Add a lock in the portal.
- Click + Add.
- Lock name:
lock-portal-no-delete. - Lock type: select Delete (this is
CanNotDelete). - Notes:
Created in portal — protects the lab storage account. - Click OK.
Expected result: the lock appears in the list with type Delete. You can also reach the same blade for a resource group by opening the group itself and choosing Locks — that is how you lock at group scope in the UI.
Step 9 — Prove the portal lock works. Still on the storage account, click Overview → Delete (top bar). Type the account name to confirm and click Delete.
Expected result: the deletion is rejected with a notification along the lines of “Failed to delete storage account … The scope … cannot perform delete operation because following scope(s) are locked.” The resource survives. Remove the lock again from the Locks blade (select it → Delete) so the resource is unlocked for the Bicep part.
Part D — Lock declaratively with Bicep
Portal and CLI are fine for one-off protection, but production locks belong in source control so they are recreated on every deployment and never silently lost.
Step 10 — Write the Bicep file. A lock is a Microsoft.Authorization/locks resource; the scope property attaches it to a parent. Save as lock.bicep:
@description('Name of the existing storage account to lock')
param storageAccountName string
// Reference the already-deployed storage account
resource stg 'Microsoft.Storage/storageAccounts@2023-05-01' existing = {
name: storageAccountName
}
// Attach a CanNotDelete lock to it
resource deleteLock 'Microsoft.Authorization/locks@2020-05-01' = {
name: 'lock-bicep-no-delete'
scope: stg
properties: {
level: 'CanNotDelete'
notes: 'Managed in Bicep — do not remove without a change ticket'
}
}
Two details that matter: existing tells Bicep to reference a resource it did not create (so you are adding a lock, not redeploying the storage account), and scope: stg is what bolts the lock onto that specific resource. To lock a resource group instead, you would deploy at subscription scope and set scope to the resource group.
Step 11 — Deploy the Bicep at resource-group scope.
az deployment group create \
--resource-group $RG \
--template-file lock.bicep \
--parameters storageAccountName=$STG \
-o table
Expected output: a deployment row with ProvisioningState = Succeeded. Validate the lock landed:
az lock list --resource-group $RG --resource $STG \
--resource-type Microsoft.Storage/storageAccounts \
--query "[].{name:name, level:level}" -o table
Expected output: a row showing lock-bicep-no-delete / CanNotDelete. Same protection, but reproducible — re-running the deployment re-asserts the lock, and committing lock.bicep makes the protection part of your infrastructure definition.
Part E — Teardown
Locks block deletion, so teardown is a deliberate two-step: remove the lock, then delete the group — the same dance you do in production whenever a locked resource genuinely must go.
Step 12 — Remove the lock.
az lock delete --name lock-bicep-no-delete -g $RG \
--resource $STG --resource-type Microsoft.Storage/storageAccounts
Expected output: no error (a silent success). Confirm no locks remain:
az lock list --resource-group $RG --resource $STG \
--resource-type Microsoft.Storage/storageAccounts -o table
Expected output: an empty result.
Step 13 — Delete the resource group (now that nothing is locked).
az group delete -n $RG --yes --no-wait
Expected output: the command returns immediately (--no-wait); group and storage account delete in the background. Had this failed with ScopeLocked, you would know a lock was still present somewhere in the group — exactly the safety behaviour the lab just taught. Your subscription is now clean.
You have now protected a resource three ways — CLI, portal, and Bicep — proven each lock blocks the operation it should, and seen why teardown is a deliberate two-step.
Common mistakes & troubleshooting
Locks are simple, but the ways they go wrong are non-obvious because the error often appears far from the lock itself. The failure modes that cost real teams real time, each with how to confirm and the fix:
| # | Symptom | Root cause | How to confirm | Fix |
|---|---|---|---|---|
| 1 | A legitimate deployment fails with ScopeLocked |
A lock (often inherited from the RG) blocks the deploy’s writes/deletes | az lock list -g <rg> shows a lock; error names “locked” |
Remove the lock, deploy, re-add it; or scope the lock narrower |
| 2 | az group delete fails even as Owner |
A CanNotDelete lock on the group or a child |
Error: “scope cannot be deleted because it has a delete lock” | Remove all locks in the group first, then delete |
| 3 | Storage tools fail to list keys / connect | ReadOnly lock blocks listKeys (a POST = write) |
Error on key-dependent action under a ReadOnly lock | Use CanNotDelete instead, or remove the ReadOnly lock |
| 4 | “I removed the lock but still can’t delete” | A second lock at another scope (inherited) | az lock list at RG and subscription scope |
Remove the inherited lock too — most restrictive wins |
| 5 | Lock command fails with authorization error | Caller lacks Microsoft.Authorization/locks/* |
az role assignment list --assignee <you> lacks Owner/UAA |
Get Owner or User Access Administrator on the scope |
| 6 | Lock “disappeared” after a redeploy | IaC redeployed the parent without the lock | Lock absent in az lock list; not in your Bicep/TF |
Define the lock in your IaC so it is re-asserted |
| 7 | Deleted a blob/row despite the lock | Locks are management-plane; data plane is separate | The resource exists; only its data changed | Use data-plane controls (immutability, soft-delete, RBAC) |
| 8 | VM won’t restart / App Service won’t scale | ReadOnly lock blocks the underlying write |
Operation works after switching to CanNotDelete | Downgrade ReadOnly → CanNotDelete for operable resources |
| 9 | Backup/restore or AKS operation fails oddly | A ReadOnly lock blocks a hidden management write | Diagnose-and-solve / activity log shows a locked-scope write | Avoid ReadOnly on resources with active control-plane ops |
The two that surprise people most: #1 is the everyday friction — an inherited CanNotDelete on a group blocks any deployment that needs to delete-and-recreate something inside it, even though you only meant to stop accidental deletion; the cure is the remove-deploy-relock dance or scoping the lock to specific resources. #3/#8 are why ReadOnly has a bad reputation: so many Azure “read” and “operate” actions are POST/PUT writes underneath that ReadOnly breaks far more than its name suggests. Live-incident shortcut: a blocked delete means a lock up the scope chain (remove it at every level); a blocked modify, restart, or key-list means a ReadOnly lock specifically; a blocked lock removal means you lack locks/*.
Best practices
A short, production-grade rule set:
- Lock by default on stateful, hard-to-rebuild resources — databases, storage, key vaults, hub VNets, DNS zones, App Service plans — with at least a
CanNotDeletelock. - Prefer
CanNotDeleteoverReadOnly. Use ReadOnly only for genuinely static resources, and only after testing it against your real operational tasks. - Lock at the resource-group scope when the whole group is precious — one group lock protects every current and future resource and is less to forget than per-resource locks.
- Never lock the subscription unless it holds nothing but locked-down shared infrastructure.
- Define locks in infrastructure-as-code so every deployment re-asserts them and a stray redeploy can never silently strip your protection.
- Name locks clearly and use the notes field —
lock-prod-no-deleteplus “remove only via change ticket” turns a cryptic guardrail into a self-documenting one. - Document the remove-deploy-relock procedure so legitimate changes are a scripted step, not a deploy-time panic.
- Combine locks with RBAC and Policy, don’t substitute — locks stop accidents, RBAC stops the wrong people, Policy stops the wrong configurations.
- Restrict who can manage locks —
Microsoft.Authorization/locks/*(Owner, User Access Administrator) is the off-switch for your guardrails. - Audit your locks periodically so you know what is protected and nothing has silently vanished or appeared.
A reference of the commands you will use to operate locks day to day:
| Task | Command |
|---|---|
| List all locks in a subscription | az lock list -o table |
| List locks on a resource group | az lock list -g <rg> -o table |
| List locks on one resource | az lock list -g <rg> --resource <name> --resource-type <type> |
| Create an RG-scope CanNotDelete lock | az lock create -n <name> --lock-type CanNotDelete -g <rg> |
| Create a resource-scope lock | az lock create -n <name> --lock-type CanNotDelete -g <rg> --resource <name> --resource-type <type> |
| Delete a lock | az lock delete -n <name> -g <rg> [--resource ... --resource-type ...] |
| Show one lock’s details | az lock show -n <name> -g <rg> |
Security notes
Locks are a safety control, not a security control — conflating the two is a mistake. A lock prevents accidental and casual destructive actions; it does not stop a determined attacker who already holds Microsoft.Authorization/locks/*, because they simply remove the lock first. Treat locks as the seatbelt, not the door lock: they protect against the mistake far more than the malice.
Locks still fit defence-in-depth. Least privilege on lock management is the real security boundary — the power to remove a lock is the power to defeat it, so the identities holding Owner or User Access Administrator on a scope should be few. Give Azure Key Vault its own CanNotDelete lock alongside its purge-protection and soft-delete, and remember the management/data-plane split: a lock on a storage account does not protect the blobs inside — pair it with blob soft-delete, versioning, and immutability, and pair a locked database with proper backup and recovery. Lock create/delete operations also appear in the Activity log, so an alert on lock removal against a production scope is a tripwire for an impending destructive action.
The least-privilege summary for lock administration:
| Built-in role | Can manage locks? | Notes |
|---|---|---|
| Owner | Yes | Full locks/*; the default lock admin |
| User Access Administrator | Yes | Has locks/* without broad resource control |
| Contributor | No | Can manage resources but not locks — by design |
| Reader | No | Read-only; cannot create or remove locks |
The Contributor row is the deliberate, useful gap: a Contributor can operate resources under a CanNotDelete lock but cannot remove the lock, so day-to-day engineers keep working while the off-switch stays with a smaller group.
Cost & sizing
There is almost nothing to size here, which is part of why locks are a no-brainer.
| Item | Cost |
|---|---|
| Creating or holding any number of locks | Free |
| Locks at any scope (sub/RG/resource) | Free |
| The lab in this article (empty Standard_LRS storage) | Effectively ₹0 — a few paise; deleted at teardown |
Locks have no SKU, no per-lock charge, and no realistic quota. The only “cost” is operational friction — the occasional need to remove a lock before a legitimate change, a few minutes of an engineer’s time — weighed against the rebuild, outage, and data loss of an accidental deletion. There is no free-tier limit because there is no charge to limit; locks work on every subscription type. Size your protection, not your spend.
Interview & exam questions
These map to AZ-104 (Azure Administrator) and AZ-900 (Azure Fundamentals) governance objectives, where locks appear regularly.
-
What are the two lock types and what does each block?
CanNotDeleteblocks deletion only; the resource can still be read and modified.ReadOnlyblocks deletion and all modifications, freezing the resource. CanNotDelete is the common choice; ReadOnly is for genuinely static resources. -
At which scopes can you apply a lock? Three: subscription, resource group, and individual resource. Locks inherit downward, so a higher-scope lock applies to all children beneath it.
-
Can a subscription Owner delete a resource with a CanNotDelete lock? Not while the lock is in place. The Owner must first remove the lock (Owners hold
Microsoft.Authorization/locks/*), then delete. Locks apply to everyone regardless of role — that is the point. -
A group has a CanNotDelete lock and a resource in it has a ReadOnly lock. What is effective on the resource? ReadOnly. When multiple locks apply, the most restrictive level wins; inheritance only adds restriction.
-
Why might a deployment fail with
ScopeLockedeven when it deletes nothing? AReadOnlylock blocks create/update operations, not just deletes — so any write fails. (CanNotDelete only blocks deletes, including delete-and-recreate steps.) -
Which built-in roles can create and remove locks? Owner and User Access Administrator, which hold
Microsoft.Authorization/locks/*. Contributor deliberately cannot manage locks, though it can operate resources under a CanNotDelete lock. -
When should you choose Azure Policy instead of a lock? When the goal is about configuration across many resources — allowed regions, required tags, denying public IPs — rather than blocking delete/modify of a specific scope. Policy evaluates configuration and scales; locks are all-or-nothing per scope.
-
Do locks protect the data inside a resource, like blobs in a storage account? No. Locks are management-plane controls that protect the resource as an ARM object. Data protection (blobs, rows, messages) comes from data-plane controls — soft-delete, versioning, immutability, data-plane RBAC.
-
What is a known side effect of a ReadOnly lock on a storage account? It blocks listing the account keys, because
listKeysis a POST (a write). Apps that fetch keys at runtime can break — a reason to prefer CanNotDelete. -
How do you make a lock survive infrastructure redeployments? Define it in IaC — a
Microsoft.Authorization/locksresource in Bicep, orazurerm_management_lockin Terraform — so every deployment re-asserts it. A portal-only lock can be silently lost if the parent is redeployed without it. -
What does “The scope cannot be deleted because it has a delete lock” mean, and the fix? A
CanNotDeletelock is on that scope or inherited from a parent. Remove the lock — and check parent scopes too, since it may come from the group or subscription. -
Why are locks a safety control, not a security control? Anyone who can manage locks can remove one and then perform the destructive action — so locks stop accidents, not a determined attacker. The real security boundary is least-privilege control over who can remove locks.
Quick check
- You want to stop anyone — including Owners — from accidentally deleting a production Key Vault, but the team must still be able to add and rotate secrets. Which lock level?
- True or false: a
CanNotDeletelock on a resource group also protects resources you add to that group next month. - A resource group has a
ReadOnlylock inherited from the subscription and aCanNotDeletelock applied directly. What can you do to the resources in it? - Which built-in role can operate a locked resource but cannot remove the lock?
- Your nightly pipeline started failing with
ScopeLockedon an update operation. What kind of lock is the most likely culprit?
Answers
- CanNotDelete. It blocks deletion (even by Owners) while allowing modifications like adding and rotating secrets. (Pair it with Key Vault soft-delete/purge protection for defence in depth.)
- True. Locks inherit to all children of the scope, including resources created after the lock — that is one of the main reasons to lock at the group scope.
- Read them, but neither modify nor delete them. The inherited
ReadOnlyis the most restrictive lock in play, and inheritance can only add restriction, so the directly-applied CanNotDelete does not loosen it — the resources are effectively read-only. - Contributor. It can manage resources (and operate ones under a CanNotDelete lock) but lacks
Microsoft.Authorization/locks/*, so it cannot create or remove locks. - A
ReadOnlylock. It blocks updates, not just deletes — a CanNotDelete lock would have let the update through. Find the ReadOnly lock (check the resource, the group, and the subscription) and remove or downgrade it.
Glossary
- Resource lock — A management-plane setting attached to a subscription, resource group, or resource that blocks delete and/or modify operations regardless of the caller’s RBAC.
- CanNotDelete — The lock level (shown as Delete in the portal) that blocks deletion but allows reads and modifications.
- ReadOnly — The lock level (shown as Read-only) that blocks deletion and all create/update operations, effectively freezing the resource.
- Scope — The level at which a lock is applied: subscription, resource group, or individual resource.
- Inheritance — The rule that a lock applied at a higher scope automatically applies to all children beneath it.
- Most restrictive wins — When multiple locks apply to the same resource, the strictest level is enforced; inheritance can only add restriction.
- Management plane — The Azure Resource Manager (ARM) API surface that creates, updates, and deletes resources; what locks intercept.
- Data plane — A resource’s own runtime API (e.g. reading a blob, querying a database); what locks do not directly control.
Microsoft.Authorization/locks/*— The RBAC permission required to create and remove locks; held by Owner and User Access Administrator.- ScopeLocked — The error code Azure returns when an operation is rejected because the target scope is locked.
listKeys— A POST (write) operation that returns a resource’s access keys; blocked by a ReadOnly lock, which is why ReadOnly can break key-dependent tools.- Azure Policy — A governance service that audits or enforces resource configurations at scale; complementary to locks.
- RBAC — Role-Based Access Control; governs who can perform which operations, the identity layer that locks sit orthogonal to.
- Infrastructure-as-code (IaC) — Defining resources (and locks) declaratively in Bicep/Terraform so they are version-controlled and reproducibly re-asserted.
Next steps
- Understand the hierarchy locks attach to, end to end, in Azure Resource Hierarchy Explained.
- Add configuration-level guardrails alongside locks with Azure Policy for Governance at Scale.
- Protect your secrets store specifically — including its own lock and purge protection — in Azure Key Vault: Secrets, Keys, and Certificates.
- Pair locks with real data durability via Back Up Your First Azure VM Step by Step.
- See where locks fit in a full governance structure in Azure Enterprise-Scale Landing Zone.