How to Troubleshoot Azure Storage Blobs
Why This Is Happening
I've seen this scenario play out more times than I can count: a developer deploys a perfectly functioning app to Azure, opens the browser, and instead of their beautifully stored blob data, they get a cryptic 403 AuthorizationPermissionMismatch or a soul-crushing 404 BlobNotFound. The Azure portal shows the storage account is running fine. The blobs are clearly there. And yet , nothing works. I know this is frustrating, especially when production is down and your team is watching.
Azure Blob Storage is one of the most powerful object storage services available, handling everything from backup archives to media streaming pipelines. But that power comes with a layered security and configuration model that trips up even seasoned Azure architects. When something goes wrong, the error messages are rarely clear about what exactly broke or where to look first.
Here's a breakdown of the most common culprits behind Azure Storage Blob troubleshooting headaches:
- Authentication failures (401/403): Expired SAS tokens, wrong access keys, missing role assignments, or Shared Key authentication being disabled at the account level.
- Network and firewall rules: A storage account firewall set to "Selected networks" with your client IP or virtual network not whitelisted , something that's caused more midnight incidents than I care to admit.
- Throttling (HTTP 503 / RequestRateTooHigh): Hitting the ingress/egress or IOPS limits for your storage account's performance tier.
- Blob lifecycle and tier issues: Trying to read a blob in the Archive access tier without rehydrating it first, Azure returns error code
BlobArchivedand most apps don't handle it gracefully. - Lease conflicts (HTTP 409 LeaseIdMissing or LeaseAlreadyPresent): An application crashed mid-write and left an active lease on a blob, blocking all subsequent write operations.
- CORS misconfigurations: Browser-based clients getting blocked on preflight requests because the storage account's CORS rules don't include the right origin or allowed methods.
- Soft delete and versioning surprises: A blob appears "deleted" but is still being billed, or a versioned blob has dozens of hidden versions eating into your storage quota.
The frustrating reality is that Azure's own error messages often only tell you that something failed, not why. A 403 can mean five completely different things depending on your auth model. This guide walks you through every layer, from the fastest one-liner fix to deep diagnostic log analysis. Browse all Microsoft fix guides →
The Quick Fix, Try This First
Before you spend an hour digging through logs, run through this rapid triage. About 60% of Azure Blob Storage access problems come down to one of these three things: an expired SAS token, a storage account firewall rule, or a missing RBAC role assignment. Let's knock these out fast.
Step 1, Regenerate and verify your connection string or SAS token. In the Azure portal, navigate to your storage account → Security + networking → Access keys. Copy the full connection string for key1 and test it directly using Azure Storage Explorer. If that works, your application's connection string is stale, update it in your app config or Key Vault secret.
If you're using a SAS token, check the expiry. Paste your SAS URI into this PowerShell snippet to decode it instantly:
# Decode SAS token expiry, paste your full SAS URI between the quotes
$sas = "https://youraccount.blob.core.windows.net/container/blob?sv=2022-11-02&se=2026-01-01T00%3A00%3A00Z&..."
[System.Web.HttpUtility]::UrlDecode(($sas -split 'se=')[1] -split '&')[0]
Step 2, Check your storage account firewall. Go to your storage account → Security + networking → Networking. If "Enabled from selected virtual networks and IP addresses" is selected, your client IP or the service's outbound IP must be in the allowlist. Click Add your client IP address to add yourself immediately and test. For Azure services like Functions or App Service, you need either a VNet integration or "Allow Azure services on the trusted services list to access this storage account" checked.
Step 3, Verify RBAC role assignments. Go to your storage account → Access Control (IAM) → Check access → search for the user, managed identity, or service principal. They need at minimum Storage Blob Data Reader for read operations, or Storage Blob Data Contributor for write operations. Owner/Contributor roles at the subscription level do NOT grant data-plane access, this surprises a lot of people.
AuthorizationPermissionMismatch means the identity exists but lacks the right data-plane role. AuthorizationFailure usually points to a bad key or expired token. PublicAccessNotPermitted means anonymous access was disabled at the account level. Each one has a completely different fix, and reading just the 403 status wastes valuable time.
You cannot troubleshoot Azure Storage blobs blind. The single highest-leverage action you can take is turning on Storage Analytics logging (or Azure Monitor resource logs) and reading what Azure is actually recording about every request. I've seen teams spend days guessing when the answer was sitting right there in the logs.
Navigate to your storage account → Monitoring → Diagnostic settings → + Add diagnostic setting. Check StorageRead, StorageWrite, and StorageDelete under the Blob category, then send logs to a Log Analytics workspace. Click Save.
Once logs are flowing (allow 5–10 minutes for the first entries), go to Monitoring → Logs and run this Kusto query to surface failed requests:
StorageBlobLogs
| where TimeGenerated > ago(1h)
| where StatusCode != 200 and StatusCode != 201 and StatusCode != 206
| project TimeGenerated, OperationName, StatusCode, StatusText, ErrorCode, CallerIpAddress, Uri
| order by TimeGenerated desc
| take 100
The ErrorCode column is your gold mine. Values like BlobNotFound, AuthorizationPermissionMismatch, RequestRateTooHigh, or BlobArchived tell you exactly what's failing. Pair the Uri column with CallerIpAddress to confirm which client and which blob path is triggering the error.
If you're using the older $logs blob container format (Storage Analytics v1), you can download logs directly via Azure Storage Explorer by navigating to the $logs container in your storage account. The log format there is pipe-delimited and less readable, but the error codes are identical.
What you should see if this worked: A list of failed requests with specific error codes. Even a single row is actionable, it tells you exactly which operation is failing and from which IP or identity.
Authentication errors on Azure Storage blobs come in flavors that look identical from the outside but need completely different fixes. Here's how to split them apart.
If you see HTTP 401 with error code NoAuthenticationInformation: The request arrived without any credentials at all. Your connection string is missing, your SAS token wasn't appended to the URL, or your Managed Identity wasn't properly granted tokens. Verify the application is actually reading the connection string from the right environment variable or Key Vault reference.
If you see HTTP 403 with AuthorizationPermissionMismatch: The identity authenticated fine but doesn't have a data-plane RBAC role. Run this PowerShell to check current role assignments:
# Replace with your storage account resource ID
$scope = "/subscriptions/{sub-id}/resourceGroups/{rg}/providers/Microsoft.Storage/storageAccounts/{account}"
Get-AzRoleAssignment -Scope $scope | Select-Object DisplayName, RoleDefinitionName, PrincipalType
Assign the correct role if it's missing:
New-AzRoleAssignment `
-ObjectId "{managed-identity-object-id}" `
-RoleDefinitionName "Storage Blob Data Contributor" `
-Scope $scope
If you see HTTP 403 with AccountIsDisabled: The storage account itself is disabled. Go to your storage account → Overview and check the status. This is rare but happens after policy enforcement in enterprise environments.
If Shared Key authentication was disabled: Azure now allows disabling Shared Key auth at the account level (a security feature). If your app uses a connection string with AccountKey, it will fail with KeyBasedAuthenticationNotPermitted. Go to Configuration → scroll to "Allow storage account key access" → enable it, or migrate your app to use Managed Identity instead, which is the right long-term fix anyway.
What you should see if this worked: Requests succeed with HTTP 200/201. Rerun your diagnostic log query, the 403 rows should stop appearing.
A 404 on Azure Blob Storage sounds simple, the blob doesn't exist. But in practice, there are five distinct scenarios that all produce a 404, and only one of them means the blob is actually gone.
Scenario A, Wrong container name or blob path: Azure blob paths are case-sensitive. MyContainer/MyBlob.jpg and mycontainer/myblob.jpg are completely different paths. Double-check casing end-to-end. Run this to list exactly what exists:
az storage blob list \
--account-name "yourstorageaccount" \
--container-name "yourcontainer" \
--output table \
--auth-mode login
Scenario B, Soft delete is on, blob appears missing: If soft delete is enabled and a blob was deleted within the retention period, it still exists but is hidden. In Azure Storage Explorer, click View → Active and soft-deleted blobs to reveal them. You can undelete with:
az storage blob undelete \
--account-name "yourstorageaccount" \
--container-name "yourcontainer" \
--name "your-blob-name" \
--auth-mode login
Scenario C, Blob in Archive tier: Archive tier blobs return BlobArchived (which some SDKs surface as a 409, others as a 404). You must rehydrate first. Set the rehydration priority to High for urgent data:
az storage blob set-tier \
--account-name "yourstorageaccount" \
--container-name "yourcontainer" \
--name "your-blob-name" \
--tier Hot \
--rehydrate-priority High \
--auth-mode login
Rehydration can take between 1 and 15 hours depending on blob size. There's no way to speed it up beyond setting High priority.
Scenario D, Container doesn't exist yet: Your app is trying to upload before the container was created. Add container creation logic with CreateIfNotExists() in your SDK code, or create it manually via the portal under Data storage → Containers → + Container.
What you should see if this worked: The blob is accessible, the undelete succeeds, or the rehydration status changes to "rehydrate-pending."
If your application is hammering Azure Blob Storage with thousands of requests per second, you'll eventually hit the scalability limits. The response is usually HTTP 503 with error code ServerBusy or HTTP 429 with RequestRateTooHigh. I know it's tempting to just retry harder, don't. That makes it worse.
First, understand the limits you're working with. A Standard general-purpose v2 storage account supports up to 20,000 requests per second, 10 Gbps ingress, and 50 Gbps egress per storage account. Premium block blob accounts have higher IOPS but the same per-request rate limits apply per partition.
Check your current metrics by going to your storage account → Monitoring → Metrics. Add these metrics to a chart:
- Transactions, total request count over time
- Availability, should be 100%; dips correlate with throttling events
- Success E2E Latency vs Success Server Latency, a big gap between these two points to client-side network issues, not server throttling
Implement exponential backoff with jitter in your application code. The Azure Storage SDK does this automatically when you use the built-in retry policies. If you're using the .NET SDK, configure it like this:
var options = new BlobClientOptions();
options.Retry.MaxRetries = 5;
options.Retry.Delay = TimeSpan.FromSeconds(2);
options.Retry.MaxDelay = TimeSpan.FromSeconds(60);
options.Retry.Mode = Azure.Core.RetryMode.Exponential;
var client = new BlobServiceClient(connectionString, options);
If you're consistently hitting limits, the right architectural fix is to distribute load across multiple storage accounts, use Azure CDN or Front Door in front of your blob endpoint for read-heavy workloads, or batch small write operations using block blob staging (put block + put block list) rather than individual put blob calls for every file.
What you should see if this worked: The Availability metric returns to 100%, Success E2E Latency stabilizes, and your application logs stop showing 503 or 429 responses.
Network-level blocks are sneaky because they often look like authentication failures or complete timeouts, not firewall rejections. If your app works fine from your laptop but fails when deployed to Azure App Service, Azure Functions, or a VM, firewall rules are almost always the answer.
For Azure service-to-service connectivity: Go to your storage account → Networking → under "Exceptions," make sure "Allow Azure services on the trusted services list to access this storage account" is checked. App Service, Functions, Logic Apps, and Azure Data Factory are all on this trusted list. This single checkbox has saved me from more production outages than I can count.
For VNet-integrated services: If your App Service or Function is VNet-integrated, you need to add the subnet to the storage account's allowed virtual networks. Under Networking → Virtual networks → + Add existing virtual network, select the VNet and subnet. Make sure the subnet has the Microsoft.Storage service endpoint enabled.
# Enable service endpoint on the subnet via PowerShell
$subnet = Get-AzVirtualNetworkSubnetConfig -Name "your-subnet" -VirtualNetwork (Get-AzVirtualNetwork -Name "your-vnet" -ResourceGroupName "your-rg")
$subnet.ServiceEndpoints = @()
$endpoint = New-AzServiceEndpointPolicyDefinition -Name "StorageEndpoint" -Service "Microsoft.Storage"
# Then update the VNet with the endpoint enabled
For CORS errors, if you're building a web app that calls blob endpoints directly from the browser, you need CORS configured on the storage account. Go to Settings → Resource sharing (CORS) → select Blob service → + Add. Fill in:
- Allowed origins: Your domain (e.g.,
https://yourapp.com), never use*in production for writable endpoints - Allowed methods: GET, PUT, DELETE, HEAD, OPTIONS
- Allowed headers:
* - Exposed headers:
* - Max age: 3600
CORS errors show up in the browser console as Access to fetch at 'https://youraccount.blob.core.windows.net/...' from origin 'https://yourapp.com' has been blocked by CORS policy. After saving the CORS rule, test with a curl preflight:
curl -I -X OPTIONS "https://youraccount.blob.core.windows.net/yourcontainer/yourblob" \
-H "Origin: https://yourapp.com" \
-H "Access-Control-Request-Method: GET"
You should see Access-Control-Allow-Origin: https://yourapp.com in the response headers. If you don't, the CORS rule didn't save correctly or the origin doesn't match exactly (trailing slash, http vs https, subdomain, all matter).
What you should see if this worked: Requests from your deployed service reach the storage account without timeout, and browser preflight requests return valid CORS headers.
Advanced Troubleshooting
If the steps above haven't cracked it, you're dealing with something deeper. Here's where I go when the standard fixes don't land.
Analyzing Event Viewer and Application Logs for SDK Errors
When your application is running on a Windows server or Azure VM, open Event Viewer (eventvwr.msc) → Windows Logs → Application. Filter by your application's source and look for entries around the time of blob failures. The Azure Storage SDK logs detailed exception traces including the RequestId, a GUID you can cross-reference directly in your Storage diagnostic logs to find the exact failed request on the server side.
Blob Lease Conflicts, Breaking Stuck Leases
If you're seeing HTTP 409 with error code LeaseAlreadyPresent or LeaseIdMissing, a blob is locked. This usually happens when an application crashed mid-operation. You can break the lease forcibly using PowerShell:
# Install Az.Storage module if needed: Install-Module Az.Storage
$ctx = New-AzStorageContext -StorageAccountName "youraccount" -UseConnectedAccount
$blob = Get-AzStorageBlob -Container "yourcontainer" -Blob "your-locked-blob.txt" -Context $ctx
# Break the lease, no lease ID needed for a forced break
$blob.ICloudBlob.BreakLease([TimeSpan]::Zero)
After breaking the lease, the blob returns to an unlocked state immediately and normal write operations can proceed.
Enterprise and Domain-Joined Scenarios
In enterprise environments, Azure Storage Blobs are often accessed through Azure Private Endpoints, meaning the storage account's public endpoint is completely disabled. If your application is using the public DNS name (youraccount.blob.core.windows.net) but the storage account only accepts connections via private endpoint, every request will fail with a timeout or connection refused, not a meaningful error.
Verify with: nslookup youraccount.blob.core.windows.net. If it resolves to a 10.x.x.x or 172.x.x.x private IP, you're going through a private endpoint and your client must be on the same VNet or connected via ExpressRoute/VPN. If it resolves to a public Azure IP but connections are failing, check if Public network access is set to "Disabled" under Networking.
Group Policy and Conditional Access
In Azure AD-integrated environments, Conditional Access policies can block token issuance for storage access. Check the Azure AD Sign-in logs (Azure Active Directory → Monitoring → Sign-in logs) and filter by the service principal or user identity making the storage calls. A Conditional Access failure shows as Status: Failure with the specific policy name listed under Conditional Access.
Immutable Storage and Legal Hold Conflicts
If you're getting HTTP 409 with BlobImmutableDueToPolicy, the container has an immutability policy or legal hold active. These are intentional, you cannot delete or overwrite blobs until the policy expires or the hold is released. Go to Data storage → Containers → select your container → Access policy to review active policies.
Checking Storage Account Health via Azure Service Health
Before spending hours diagnosing what might be an Azure platform incident, always check Azure Service Health → Service issues and filter by region and the Azure Storage service. Platform-side degradation events happen occasionally and no amount of configuration changes will fix them, you just have to wait. Set up a Service Health alert so you get email or webhook notifications for future events.