Azure Governance

Azure Resource Locks: Prevent Accidental Deletes and Changes in Production

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:

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:

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.

  1. Open the Azure portal and go to Resource groups → rg-lock-lab.
  2. Click your storage account (stglocklab…).
  3. 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.

  1. Click + Add.
  2. Lock name: lock-portal-no-delete.
  3. Lock type: select Delete (this is CanNotDelete).
  4. Notes: Created in portal — protects the lab storage account.
  5. 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:

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.

  1. What are the two lock types and what does each block? CanNotDelete blocks deletion only; the resource can still be read and modified. ReadOnly blocks deletion and all modifications, freezing the resource. CanNotDelete is the common choice; ReadOnly is for genuinely static resources.

  2. 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.

  3. 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.

  4. 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.

  5. Why might a deployment fail with ScopeLocked even when it deletes nothing? A ReadOnly lock blocks create/update operations, not just deletes — so any write fails. (CanNotDelete only blocks deletes, including delete-and-recreate steps.)

  6. 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.

  7. 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.

  8. 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.

  9. What is a known side effect of a ReadOnly lock on a storage account? It blocks listing the account keys, because listKeys is a POST (a write). Apps that fetch keys at runtime can break — a reason to prefer CanNotDelete.

  10. How do you make a lock survive infrastructure redeployments? Define it in IaC — a Microsoft.Authorization/locks resource in Bicep, or azurerm_management_lock in Terraform — so every deployment re-asserts it. A portal-only lock can be silently lost if the parent is redeployed without it.

  11. What does “The scope cannot be deleted because it has a delete lock” mean, and the fix? A CanNotDelete lock 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.

  12. 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

  1. 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?
  2. True or false: a CanNotDelete lock on a resource group also protects resources you add to that group next month.
  3. A resource group has a ReadOnly lock inherited from the subscription and a CanNotDelete lock applied directly. What can you do to the resources in it?
  4. Which built-in role can operate a locked resource but cannot remove the lock?
  5. Your nightly pipeline started failing with ScopeLocked on an update operation. What kind of lock is the most likely culprit?

Answers

  1. 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.)
  2. 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.
  3. Read them, but neither modify nor delete them. The inherited ReadOnly is 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.
  4. Contributor. It can manage resources (and operate ones under a CanNotDelete lock) but lacks Microsoft.Authorization/locks/*, so it cannot create or remove locks.
  5. A ReadOnly lock. 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

Next steps

AzureResource LocksGovernanceCanNotDeleteReadOnlyRBACBicepAzure CLI
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