Goal
A production storage account is LRS, so one zone outage in the region can make it unavailable. You want ZRS, or GZRS later, without a data copy, a new account name or a connection-string change.
Customer-initiated conversion does this in place. It's a different operation from the SKU change that adds or removes geo-redundancy:
| Change | Method | Downtime |
|---|---|---|
| LRS ↔ GRS / RA-GRS, GRS → LRS | az storage account update --sku | None |
| LRS → ZRS, GRS → GZRS, ZRS → LRS and the like (zone redundancy added or removed) | Conversion: az storage account migration start, portal or support request | None. Accounts with hierarchical namespace may see a pause of under 30 seconds |
| Anything else that conversion blocks, or a hard deadline | Manual migration to a new account (AzCopy and others) | App cut-over needed |
Zone changes and geo changes are separate steps. To go from LRS to GZRS, do the conversion to ZRS, wait at least 24 hours, then change the SKU to GZRS. The other order also works.
Step 1: inventory the account
RG=rg-data
SA=stproddata01
az storage account show -g $RG -n $SA --query "{sku:sku.name, kind:kind, tier:accessTier, hns:isHnsEnabled, nfsv3:enableNfsV3, location:location, secondary:secondaryLocation, failoverInProgress:failoverInProgress}" -o jsonc
Check the target SKU is offered in the region. Not every region has availability zones, and ZRS and GZRS support differ by region, so check the current ZRS/GZRS region list in the Azure Storage redundancy documentation. A quick sanity check is whether the region lists zones for VMs:
az vm list-skus -l $(az storage account show -g $RG -n $SA --query location -o tsv) \
--resource-type virtualMachines --query "[0].locationInfo[0].zones" -o tsv
Step 2: clear the blockers
Each of these stops or fails a conversion. Check them all before you submit.
| Blocker | How to check | What to do |
|---|---|---|
| Account kind is GPv1, or premium block/page blob | kind, sku from Step 1 | Upgrade GPv1 to GPv2 first. Premium block blob needs a manual migration. Premium page blob can't be zone-redundant |
| Blobs in the archive tier | Blob inventory, or a listing (below) | ZRS has no archive tier. Move archived blobs to another account, or rehydrate them, before you convert |
| NFSv3 enabled for Blob | enableNfsV3: true | Conversion isn't supported. Plan a manual migration |
| Azure Files NFSv4.1 shares with public endpoint access | File share protocol and network settings | Disable public endpoint access, convert, then reconfigure |
| Account used for VM boot diagnostics | VMs pointing at this account | Disable boot diagnostics against it, or point VMs at managed boot diagnostics |
| Account was failed over (GRS → now LRS in the new primary) | Account history and secondaryLocation | Conversion isn't supported from that state. Use a manual migration |
| A recent redundancy change | Activity Log | Wait 24 hours after a zone conversion before the next change |
Find archive-tier blobs. On large accounts, use blob inventory rather than a listing:
for c in $(az storage container list --account-name $SA --auth-mode login --query "[].name" -o tsv); do
n=$(az storage blob list --account-name $SA --auth-mode login -c "$c" \
--query "length([?properties.blobTier=='Archive'])" -o tsv --num-results "*")
[ "$n" != "0" ] && echo "$c: $n archive blobs"
done
Find VMs whose boot diagnostics write to this account:
az vm list --query "[?diagnosticsProfile.bootDiagnostics.storageUri && contains(diagnosticsProfile.bootDiagnostics.storageUri, '$SA')].{vm:name, rg:resourceGroup}" -o table
# Switch a VM to managed boot diagnostics
az vm boot-diagnostics enable -g <vm-rg> -n <vm-name>
Calling az vm boot-diagnostics enable without --storage switches the VM to managed storage.
Warning
Don't delete archived blobs to get past the blocker without checking retention rules. Archived data is often there for legal or audit reasons. Early deletion from archive is charged, and rehydrating costs money and can take hours per blob. Agree the plan with the data owner first.
Step 3: confirm nothing depends on LRS behaviour
- Locks: a
ReadOnlylock on the account or resource group blocks the change. ACanNotDeletelock doesn't. - Policy: a policy that denies SKU changes, or that pins
sku.name, will block the request. Check for an assignment that allows only certain SKUs. - IaC drift: if Bicep or Terraform declares
Standard_LRS, the next pipeline run will try to change it back, or fail. Update the code toStandard_ZRSin the same change. Some Terraform provider versions treat a replication change as a replacement, so read the plan before you apply anything.
az lock list -g $RG -o table
az policy state list --resource "$(az storage account show -g $RG -n $SA --query id -o tsv)" \
--query "[?complianceState=='NonCompliant'].{policy:policyDefinitionName, assignment:policyAssignmentName}" -o table
Step 4: start the conversion
az storage account migration start \
--account-name $SA -g $RG \
--sku Standard_ZRS \
--name default \
--no-wait
PowerShell:
Start-AzStorageAccountMigration -AccountName stproddata01 -ResourceGroupName rg-data -TargetSku Standard_ZRS -AsJob
--name is always default for customer-initiated conversions. If the request is rejected straight away, the error usually names the blocker: archive blobs, boot diagnostics, an unsupported region or an unsupported feature. Fix it and submit again.
Step 5: track it
az storage account migration show --account-name $SA -g $RG -o jsonc
Get-AzStorageAccountMigration -AccountName stproddata01 -ResourceGroupName rg-data |
Select-Object DetailMigrationStatus, DetailTargetSkuName
The status moves from submitted, to in progress, to completed or failed. Exact strings vary by API version. The conversion usually starts within 72 hours of the request and has no completion SLA. Large accounts can take much longer. A failed conversion needs a support request.
Tip
If you have a hard deadline, such as a zone resilience audit or a planned zone maintenance window, a conversion is the wrong tool. Do a manual migration with AzCopy to a new ZRS account, where you control the timing.
While it runs, the account stays fully available. Keep normal monitoring on, and watch the account's Availability and SuccessE2ELatency metrics for anything unusual:
SA_ID=$(az storage account show -g $RG -n $SA --query id -o tsv)
az monitor metrics list --resource $SA_ID --metric Availability SuccessE2ELatency \
--interval PT1H --aggregation Average -o table
Step 6: verify and finish
az storage account show -g $RG -n $SA --query "sku.name" -o tsv
# Standard_ZRS
Then:
- Update IaC to the new SKU and run a plan or what-if. It should show no changes.
- If you're going on to GZRS, wait at least 24 hours, then run
az storage account update -g $RG -n $SA --sku Standard_GZRS. Adding geo-redundancy incurs a one-time egress charge to copy the whole account to the secondary region. - Re-enable anything you turned off for the conversion, such as NFSv4.1 public endpoints, once it's complete.
- Record the change and the new SKU in the DR documentation.
Costs
The conversion itself carries no up-front charge. After it, ZRS storage and transactions cost more per GB than LRS, so check the current pricing for your region and tier before you roll this out to a whole fleet. Rehydrating archived data, and moving it to another account, are charged separately.
Prevent it next time
- Make ZRS (or GZRS) the default in your storage module, and add an audit policy that flags LRS accounts in production subscriptions.
- Keep boot diagnostics on managed storage, so VM accounts never block a redundancy change.
- Keep archive-tier data in dedicated accounts that stay on LRS or GRS by design.
- Batch conversions by region, and record the request date for each, so a "still submitted after 72 hours" account is spotted and raised with support.