CoursesAzure Administrator AssociateStorage accounts & redundancy

Storage accounts & redundancy

Services, LRS/ZRS/GRS, securing access.

Beginner30 min · lesson 7 of 15

You have one irreplaceable notebook and a photocopier. Three copies in the same desk drawer survive a spilled coffee, but not an office fire. Spread the copies across three buildings in the same city and they survive the fire, but not the earthquake that flattens the city. Mail a set to a branch office a few hundred kilometres away and it survives the earthquake too. The catch is the post. The branch copy always runs a little behind the original. Azure storage redundancy is that exact decision, priced per copy: LRS (locally redundant storage) is the desk drawer, ZRS (zone-redundant storage) is the three buildings, GRS (geo-redundant storage) is the branch office, and the postal delay has a name, *asynchronous replication*, meaning the far copy is made in the background after your write has already been accepted.

A storage account is the Azure resource that holds the data and makes that copy decision on its behalf. Two things set it apart from a folder. First, its name has to be unique across every Azure customer on the planet, because the name becomes a public DNS name (Domain Name System, the internet's address book) like contosodata.blob.core.windows.net. Second, one account is four services under one roof: Blob for loose objects such as images, backups and logs; Files for managed network shares that speak SMB and NFS (Server Message Block and Network File System, the file-sharing protocols Windows and Linux already use); Queue for small messages passed between apps; and Table for simple key-value records. Redundancy, network rules, encryption and access keys are all set on the *account*, and they apply to everything inside it. That is why account design belongs to an administrator rather than to whoever happens to be writing the app, and why AZ-104 keeps circling back to it.

Creating an account: three choices you are stuck with

Three settings on the create command follow the account for life. Kind should be StorageV2 (general purpose v2) for almost anything you build. Version 1 exists for old accounts that were never migrated, while BlockBlobStorage and FileStorage are premium kinds backed by SSDs (solid state drives, flash storage with no moving parts), each dedicated to a single service. Performance is either Standard, which sits on spinning disks and bills you per gigabyte stored, or Premium, which sits on SSDs, is paid for up front by size, and exists for workloads that count milliseconds. Name rules bite people constantly: 3 to 24 characters, lowercase letters and digits only, unique across the whole of Azure, because that string turns into a hostname. Check the name is free before you write a script that assumes it.

create-account.sh
# Account names are global DNS -- check availability first
az storage account check-name --name contosodata
# {
# "message": null,
# "nameAvailable": true,
# "reason": null
# }
az group create -n storage-rg -l westeurope --output none
az storage account create \
--resource-group storage-rg \
--name contosodata \
--location westeurope \
--kind StorageV2 \
--sku Standard_ZRS \
--access-tier Hot \
--min-tls-version TLS1_2 \
--allow-blob-public-access false \
--query "{name:name, sku:sku.name, kind:kind, blobEndpoint:primaryEndpoints.blob}"
# {
# "blobEndpoint": "https://contosodata.blob.core.windows.net/",
# "kind": "StorageV2",
# "name": "contosodata",
# "sku": "Standard_ZRS"
# }

Two habits are hiding in that command. The first is --query. By default az prints a wall of JSON (JavaScript Object Notation, the text format Azure answers in), and the little query language it accepts, JMESPath, trims the reply down to the handful of fields you actually wanted. The second habit is setting the TLS floor (Transport Layer Security, the encryption that puts the S in HTTPS) and the public-access rule *at creation time* rather than later. Accounts that show up in breach write-ups are usually the ones where someone meant to tighten this next sprint.

Redundancy: how many copies, and how far apart

Every write to Azure Storage lands on at least three copies. The SKU you pick (stock keeping unit, Azure's word for a priced product tier) decides where those three copies live. LRS keeps all three inside one datacenter: eleven nines of durability (99.999999999% of objects survive a given year), the cheapest option on the menu, and gone if that one building floods. ZRS puts the three copies in three separate *availability zones*, which are distinct datacenters inside one region with their own power, cooling and network feeds. That buys twelve nines and survival of a whole zone going dark. GRS takes the LRS layout and copies everything, in the background, to Azure's designated *paired region* hundreds of kilometres away (West Europe pairs with North Europe). Sixteen nines. GZRS (geo-zone-redundant storage) is ZRS at home plus that geo copy, and it is the production default for data you would be fired for losing. The RA- prefix (read-access, as in RA-GRS and RA-GZRS) makes the far copy readable at a -secondary endpoint. Without it, the second region is a sealed vault you can only open by failing over.

The timing behind those copies decides how incident day goes, and the exam leans on it. Copies inside the primary region are synchronous: your write is not acknowledged until all three have committed. The cross-region copy is asynchronous: a background catch-up that usually lands within 15 minutes and carries no SLA (service level agreement, the promise Microsoft will actually pay out on) for how far behind it drifts.

Choosing a redundancy SKU: what must the data survive?
Which redundancy SKU?
Set per account, never per container or share
Cheapest; only a disk or rack failure
LRS
3 synchronous copies in one datacenter · 11 nines · gone if that datacenter floods
A full availability-zone outage
ZRS
3 synchronous copies across 3 zones · 12 nines · separate power, cooling, network
A regional disaster
GRS
LRS plus a background copy to the paired region · 16 nines · secondary sealed unless RA-
Zone loss AND region loss (prod default)
GZRS
ZRS at home plus the background geo copy · for data you cannot lose
Add the RA- prefix (RA-GRS, RA-GZRS) to read the secondary endpoint. Premium performance supports LRS and ZRS only, with no geo options.

Redundancy is not permanent, but the two directions of change behave nothing alike. Adding or removing the *geo* copy (LRS to GRS, ZRS to GZRS, or back again) is an ordinary SKU switch with az storage account update. It returns almost instantly, needs no downtime, bills a one-time egress charge while the secondary is seeded, and cannot be failed over until that first sync completes. Adding or removing *zone* redundancy is a different animal. It is a conversion, with its own dedicated command, in which Azure physically shuffles your data across zones in the background. A conversion normally *begins* within 72 hours of the request and sometimes takes longer, has no SLA on completion, and needs no downtime. Once it does finish, you have to wait at least another 72 hours before you change the account's redundancy again. Two hard walls: premium performance offers LRS and ZRS only, with no geo options, and a few newer regions have no paired region at all, so GRS is not offered there.

redundancy.sh
# What redundancy is the estate actually running?
az storage account list \
--query "[].{name:name, sku:sku.name, region:primaryLocation}" -o table
# Name Sku Region
# ----------- ------------ ----------
# contosodata Standard_ZRS westeurope
# legacylogs Standard_LRS westeurope
# Adding geo-replication is a zero-downtime SKU switch:
az storage account update -g storage-rg -n legacylogs \
--sku Standard_RAGRS --query sku.name
# "Standard_RAGRS"
# Adding ZONE redundancy is a CONVERSION -- a dedicated command,
# not an update:
az storage account migration start -g storage-rg \
--account-name auditarchive --sku Standard_ZRS \
--name default --no-wait --yes
az storage account migration show -g storage-rg \
--account-name auditarchive --name default \
--query "{status:migrationStatus, target:targetSkuName}"
# {
# "status": "SubmittedForConversion",
# "target": "Standard_ZRS"
# }

Geo-replication health, and the call to fail over

If you run GRS or GZRS, one timestamp defines your disaster story: Last Sync Time. Every write stamped before it is safely in the second region. Everything after it exists only in the primary. That gap is your real RPO (recovery point objective, the amount of recent work you accept losing in a disaster). Read it *before* you fail over, because failing over is precisely the decision to abandon that work.

geo-failover.sh
# How far behind is the secondary? lastSyncTime = your real RPO.
az storage account show -g storage-rg -n legacylogs \
--expand geoReplicationStats --query geoReplicationStats
# {
# "canFailover": true,
# "canPlannedFailover": true,
# "lastSyncTime": "2026-07-13T10:41:07+00:00",
# "postFailoverRedundancy": "Standard_LRS",
# "postPlannedFailoverRedundancy": "Standard_RAGRS",
# "status": "Live"
# }
# Primary region is gone: promote the secondary (see warning below)
az storage account failover -g storage-rg -n legacylogs --yes --no-wait
# DR drills while the primary is still healthy: planned failover
# swaps roles with no data loss expected and keeps geo-redundancy
az storage account failover -g storage-rg -n legacylogs \
--failover-type Planned --yes
Unplanned failover drops your account to LRS
Customer-initiated failover promotes the secondary, and everything written after lastSyncTime is permanently lost. The API tells you the ending before you start: postFailoverRedundancy reads Standard_LRS. The copy in the original primary region is deleted, so your geo-redundant account becomes three copies in one datacenter until you re-enable GRS. Re-enabling it re-replicates every byte back across regions, billed as bandwidth, over hours or days. Rehearse disaster recovery with --failover-type Planned instead, which requires both regions to be healthy, swaps their roles, and keeps geo-redundancy intact. Save unplanned failover for the day nothing else is left.

Who gets in the front door

Every account is born with two access keys. A key is one long string that grants read, write and delete across every service in the account, with no identity attached and no record of who used it. Treat one the way you treat the domain administrator password: never in source code or a config file, parked in Key Vault if some legacy app truly needs it, rotated on a schedule. Two keys exist for exactly that reason, so you can renew one while your apps keep running on the other. A shared access signature (SAS) is the polite version: a signed URL that grants named permissions on named resources and then expires. The answer you actually want is neither of those. Microsoft Entra ID (Azure's identity service, previously called Azure AD) with data-plane RBAC roles (role-based access control, the same grant model from the RBAC lesson, pointed at the data rather than the resource) gives access to named identities you can audit and revoke. Storage Blob Data Reader and Storage Blob Data Contributor are the two you will assign most.

Network posture is set on the account as well. Force HTTPS, put the TLS floor at 1.2, switch off anonymous blob access, and set the firewall's default action to *Deny* so only networks you named can connect. The Private Endpoints lesson closes that door the rest of the way. Encryption at rest is not a setting you can get wrong: Storage Service Encryption applies 256-bit AES (Advanced Encryption Standard, the workhorse cipher of modern security) to everything, always, and there is no off switch. Where a compliance regime insists that you hold the keys yourself, you can point the account at customer-managed keys in Key Vault.

harden.sh
# Baseline hardening every account should carry:
az storage account update -g storage-rg -n contosodata \
--https-only true \
--min-tls-version TLS1_2 \
--allow-blob-public-access false \
--default-action Deny \
--bypass AzureServices \
--query "{https:enableHttpsTrafficOnly, tls:minimumTlsVersion, publicBlobs:allowBlobPublicAccess, firewall:networkRuleSet.defaultAction}"
# {
# "firewall": "Deny",
# "https": true,
# "publicBlobs": false,
# "tls": "TLS1_2"
# }
# Keys: check their age, then rotate one at a time
az storage account keys list -g storage-rg -n contosodata \
--query "[].{key:keyName, created:creationTime}" -o table
# Key Created
# ----- --------------------------------
# key1 2026-07-13T09:58:41.522041+00:00
# key2 2026-07-13T09:58:41.522041+00:00
az storage account keys renew -g storage-rg -n contosodata --key key1 --output none
# Gold standard: shared-key auth off entirely -- Entra ID or nothing
az storage account update -g storage-rg -n contosodata \
--allow-shared-key-access false --output none

One caution on that last command. Turning off shared-key access breaks anything still signing in with an account key or an account SAS, and that includes Azure Files SMB mounts, which authenticate with the account key. Inventory the callers first. It is the right place to end up, reached on purpose rather than by surprise.

What it costs, and where the exam sets traps

Redundancy multiplies every gigabyte you store. As a rough guide, ZRS costs about a quarter more than LRS, and the geo options roughly double the bill, on top of a per-gigabyte bandwidth charge for the cross-region post run. Scale limits rarely pinch a real workload but do turn up on the exam: one standard account holds 5 PiB by default (pebibytes; a pebibyte is a bit over a million gigabytes), and a subscription starts with 250 accounts per region. The traps AZ-104 keeps reusing: a GRS secondary is *not readable* unless you chose RA-GRS; redundancy belongs to the account, never to a container or a share; asynchronous replication has *no* lag SLA, so lastSyncTime is the only truth you have; premium performance means LRS or ZRS; and after an unplanned failover the account runs as LRS until you deliberately turn geo-redundancy back on.

The account is the vault. What you keep inside it is mostly blobs. Next you step into the Blob service itself: containers, the access tiers from Hot down to Archive, and lifecycle rules that demote aging data on their own, so the redundancy you picked here does not quietly become a billing problem. That is *Blob storage & lifecycle*.

One practical note before the lab. Pick account names from a scheme you can live with, because an account cannot be renamed. The name is a public hostname, so it is lowercase, unique worldwide, and it follows you into every connection string and every support ticket. A pattern built from organisation, workload and environment (contosobackupsprod, say) reads far better in an audit than a name someone typed at 4pm on a Friday. Give redundancy the same thought up front, since the zone half of that choice only moves through a conversion with no promise about when it finishes.

The day-one security list is short enough to memorise. HTTPS only. Minimum TLS 1.2. allowBlobPublicAccess set to false. Firewall default action Deny. A dated plan to disable shared-key access once your apps authenticate as managed identities. Private Endpoints for data sensitive enough that a public endpoint should not exist at all. And treat the two account keys as the data-plane super-user passwords they are: rotate them, keep them in Key Vault if you have to use them, and move applications onto Microsoft Entra ID authorization as quickly as you can.

Try this

Build a storage account with the TLS floor at 1.2 and public blob access switched off, then read the SKU and the transport settings back to prove they took. Notice while you are there that shared-key access is a switch you can flip later, once every caller has an identity of its own.

terminal
RG=rg-lab-sa
az group create -n $RG -l eastus
az storage account create -g $RG -n contosolabsa$RANDOM -l eastus \
--sku Standard_LRS --kind StorageV2 --min-tls-version TLS1_2 \
--allow-blob-public-access false
az storage account show -g $RG --name $(az storage account list -g $RG --query "[0].name" -o tsv) \
--query "{sku:sku.name,https:enableHttpsTrafficOnly,tls:minimumTlsVersion,publicBlob:allowBlobPublicAccess}" -o json
output
$ az storage account show ... --query "{sku:sku.name,https:enableHttpsTrafficOnly,tls:minimumTlsVersion,publicBlob:allowBlobPublicAccess}" -o json
{
"sku": "Standard_LRS",
"https": true,
"tls": "TLS1_2",
"publicBlob": false
}
# Sample output — LRS is cheapest; ZRS/GRS trade money for zone or region durability.

Takeaway

Hold on to this: one storage account serves blob, file, queue and table endpoints behind a single globally unique name, and redundancy (LRS, ZRS, GRS, GZRS) is a durability decision you make when you create it, for most SKUs.

Where to go next: shut the public network path, put Microsoft Entra ID with data-plane RBAC roles ahead of account keys, and give every production account a Private Endpoint.

Quick check
01The primary region of a GRS storage account goes down and you run a customer-initiated (unplanned) failover. What state is the account in the moment it finishes?
Correct — postFailoverRedundancy already warned you it would read Standard_LRS. The old primary copy is deleted, and anything written past lastSyncTime never reached the secondary, so it is gone.
Incorrect — No. The original primary copy is deleted and the account drops to LRS. You have to re-enable geo-redundancy yourself, and that copies every byte across regions over hours or days.
Incorrect — No. Failover promotes the secondary into the primary role and flattens the account to LRS. There is no second region left to read from.
Incorrect — No. Only the three copies inside the primary region are synchronous. The geo copy runs in the background with no lag SLA, so writes after lastSyncTime were never in the secondary.
02An account uses geo-redundant storage (GRS). Nothing is broken and no outage is under way. What access do you have to the copy sitting in the secondary paired region?
Incorrect — No. Automatic read access to the secondary endpoint is what the RA- prefix buys you. Plain GRS does not include it.
Incorrect — No. The secondary never accepts writes during normal operation. Writes go to the primary only.
Correct — Without the RA- prefix the second region is a sealed vault you can open only by failing over.
Incorrect — No. Plain GRS exposes no read endpoint at all. lastSyncTime measures replication lag, it does not open a way in.
03Backup blobs for a production database have to survive both the loss of an entire availability zone and the loss of the whole Azure region. Nobody needs to read the second copy directly. Which redundancy option covers both needs at the LOWEST cost?
Incorrect — No. LRS keeps all three copies in one datacenter, so it survives neither a zone outage nor a regional disaster.
Incorrect — No. ZRS spreads copies across three zones but keeps nothing outside the region, so a regional disaster still takes the data.
Incorrect — It survives both, but it also pays for a readable secondary endpoint that nobody here asked for, so it is not the cheapest fit.
Correct — Zone redundancy at home covers the zone outage, the background geo copy covers the regional disaster, and you pay nothing for RA- read access.

Related