Blob data protection — soft delete, versioning, change feed, point-in-time restore, immutability and object replication
Which blob protection features to turn on, what each one requires and blocks — blob and container soft delete (1–365 days), versioning and who may delete versions, change feed, point-in-time restore (needs all three, retention less than soft delete), immutability (time-based retention, legal hold, locked policies, account deletion blocked), object replication (versioning on both sides, read-only destination, 409), and what isn't supported with a hierarchical namespace — with Bicep and CLI.
markdownreferencev1P6BT-4A7Fstage: designtrust: officialservice: storageinterface: iacinterface: cliblob soft deletecontainer soft deletedeleteRetentionPolicycontainerDeleteRetentionPolicyisVersioningEnabledblob versioningdeleteBlobVersion/actionStorage Blob Data Owner delete versionSAS permission xchange feed$blobchangefeedrestorePolicypoint-in-time restoreaz storage blob restoreUndelete BlobRestore Containerimmutable storagetime-based retention policylegal holdlocked immutability policyversion-level WORM--enable-alwBlobImmutableDueToPolicyobject replicationobjectReplicationPoliciesaz storage account or-policy createAllowCrossTenantReplicationdestination container write 409recover deleted storage account 14 dayshierarchical namespace versioning not supportedaz storage account blob-service-properties update
Blob data protection
What each feature does and needs
| Feature | Protects against | Requires / limits |
|---|---|---|
| Blob soft delete | Deleted or overwritten blobs, snapshots, versions | Retention 1–365 days (Microsoft recommends at least 7). Restore with Undelete Blob; soft-deleted data can't be read until restored, and is billed as active data |
| Container soft delete | A deleted container and everything in it | Retention 1–365 days (default 7). Restores only to the original name: if a new container took the name, rename or delete it first. Not retroactive. Blocks upgrading a flat account to a hierarchical namespace while on |
| Versioning | Overwrites and deletes (every write makes a version) | Not on accounts with a hierarchical namespace, nor for blobs written with Data Lake APIs. All versions of a blob must be the same blob type. Can't be disabled while object replication policies exist |
| Change feed | Audit log of every change, in $blobchangefeed | Not on hierarchical-namespace accounts. Retention 1–146,000 days, or indefinite by default. $blobchangefeed doesn't appear in List Containers: list its blobs directly |
| Point-in-time restore | Bulk corruption or deletes across block blobs | Standard GPv2 only, hot and cool block blobs, no hierarchical namespace. Needs soft delete, versioning and change feed all on, and its retention must be at least one day less than blob soft delete's. Doesn't bring back deleted containers |
| Immutability (WORM) | Any change or delete, even by admins | Time-based retention (1–146,000 days) and/or legal hold, at container or version level (version level needs versioning) |
| Object replication | Region loss, as an async copy of block blobs | GPv2 or premium block blob on both sides, versioning on both, change feed on the source; no hierarchical namespace; no customer-managed failover on either account |
Neither soft delete nor versioning survives the deletion of the storage account: put a CanNotDelete lock on it (reference/arm/locks-and-tags.md). A deleted account can sometimes be recovered within 14 days if no account with the same name has been created since.
Turning it on
// Blob data protection on an existing StorageV2 account (flat namespace)
param storageAccountName string
@minValue(7)
@maxValue(365)
param softDeleteDays int = 14
resource sa 'Microsoft.Storage/storageAccounts@2026-06-01' existing = {
name: storageAccountName
}
resource blobs 'Microsoft.Storage/storageAccounts/blobServices@2026-06-01' = {
parent: sa
name: 'default'
properties: {
deleteRetentionPolicy: {
enabled: true
days: softDeleteDays
}
containerDeleteRetentionPolicy: {
enabled: true
days: softDeleteDays
}
isVersioningEnabled: true
changeFeed: {
enabled: true
retentionInDays: 90
}
restorePolicy: {
enabled: true
days: softDeleteDays - 1 // must be less than the blob soft delete days
}
}
}
CLI equivalent:
az storage account blob-service-properties update -g <rg> -n <acct> \
--enable-delete-retention true --delete-retention-days 14 \
--enable-container-delete-retention true --container-delete-retention-days 14 \
--enable-versioning true --enable-change-feed true --change-feed-days 90 \
--enable-restore-policy true --restore-days 13
Restore to a time (UTC, inside the retention window): az storage blob restore -g <rg> --account-name <acct> -t 2026-10-19T08:00:00Z (optionally -r <start> <end> blob ranges). One restore runs at a time per account, can't be cancelled, and needs Storage Account Contributor at account scope.
Versions and who may delete them
Deleting the current version needs …/blobs/delete (Storage Blob Data Contributor). Deleting a previous version needs …/blobs/deleteBlobVersion/action, which only Storage Blob Data Owner has built in; a SAS needs the x permission. With soft delete on, a deleted version is kept until soft delete's retention ends. Versions are kept until deleted: use a lifecycle management rule to tier or delete old ones, or costs grow with every write. Stop taking snapshots of block blobs once versioning is on.
Immutability
- Unlocked policies (for testing) can be shortened, extended or deleted. Locked policies can only be extended, at most five times at container level, and can't be deleted. Lock within about a day for SEC 17a-4(f) compliance.
- Writes and deletes fail with 409
BlobImmutableDueToPolicy/BlobImmutableDueToLegalHold(reference/storage/data-errors.md). - The account can't be deleted while any container has version-level immutability enabled, or while a legal hold or a locked policy is in effect at container scope.
- CLI:
az storage container immutability-policy create|lock|extend,az storage container legal-hold set|clear; account-level version WORM at creation withaz storage account create --enable-alw.
Object replication
Create the policy on the destination account first, then the same policy (with the returned policy ID) on the source: az storage account or-policy create. Up to 1,000 rules per policy, one source and one destination container each, both existing beforehand.
| Symptom | Cause → fix |
|---|---|
| 409 writing to a destination container | Destination containers are read-only while a rule targets them: remove the rule or the policy first |
| Can't disable versioning | Delete the object replication policies first |
| Policy create fails naming only the account | Use the full resource ID; cross-tenant replication is off by default for accounts created since December 15, 2023 (allowCrossTenantReplication) |
Can't set allowCrossTenantReplication false | Existing cross-tenant policies: delete them first |