Back to blog
AzureBest PracticesCloud SecurityMonitoring & LoggingOperations & Compliance

Azure Monitor Log Profile Has No Storage Account: Why It Matters and How to Fix It

Learn why an Azure Monitor log profile without a storage account puts your audit trail at risk, plus CLI, Terraform, and Azure Policy fixes to archive activity logs.

TL;DR

This check flags Azure Monitor log profiles that have no storage account set for archival, which means your activity logs can age out and disappear after the retention window. Fix it by attaching a storage account to the log profile with a sensible retention policy so you keep an immutable, long-term record of subscription activity.

Azure Monitor activity logs tell the story of what happened in your subscription: who created a resource, who changed a network security group rule, who deleted a key vault. When an incident hits, those logs are often the only thread you can pull to reconstruct the timeline. The problem is that activity logs only live for 90 days by default. If you want anything older, you need to ship them somewhere durable.

The monitor_nostorage check looks at your Azure Monitor log profile and reports when no storage account is configured for log archival. Without one, your activity log history is capped at the platform retention limit and there is no long-term copy you can fall back on.


What this check detects

A log profile in Azure Monitor controls how activity logs are exported and retained. It can route logs to a storage account for archival, to an event hub for streaming, or both. This check inspects the log profile and fails when the storageAccountId property is empty or missing.

Note: Log profiles are the classic mechanism for exporting the subscription-level activity log. Microsoft now recommends diagnostic settings as the modern replacement, but many subscriptions still run on log profiles, and the same archival gap applies to both. If you have moved to diagnostic settings, the equivalent fix is making sure the setting targets a storage account.

In short, this check answers one question: if your activity logs were needed six months from now, would they still exist? If no storage account is attached, the answer is no.


Why it matters

Activity logs are evidence. They are also one of the cheapest forms of insurance you can buy in the cloud. Here is where the gap bites in practice.

Incident investigation falls apart

Most security investigations start days or weeks after the initial compromise, not minutes. Attackers who gain a foothold often sit quietly before acting. If your only activity log record is a rolling 90-day window with no archive, an investigation that begins late may find the relevant entries already purged. You end up trying to explain a breach with half the timeline missing.

Compliance requirements go unmet

Frameworks like PCI DSS, HIPAA, SOC 2, and ISO 27001 expect audit trails to be retained well beyond 90 days, frequently for one year or more. PCI DSS, for example, calls for at least a year of log retention. A log profile with no storage account cannot satisfy that on its own, and an auditor will flag it.

Tampering and cleanup are easier to hide

An attacker who reaches a privileged role can change configurations, and the short retention window works in their favor. With logs archived to a separate storage account, especially one with immutability enabled, you keep a copy that survives even if the live retention is shortened.

Warning: Configuring a storage account for archival is not enough on its own if that account lives in the same blast radius. An attacker with subscription owner rights could delete the storage account too. Use immutable blob policies and, ideally, a separate subscription or tenant boundary for long-term log retention.


How to fix it

You need a storage account to archive into, then a log profile (or diagnostic setting) pointing at it. The steps below assume the Azure CLI is installed and you are logged in with az login.

1. Create or pick a storage account

az storage account create \
  --name lensixactivitylogs \
  --resource-group security-logging-rg \
  --location eastus \
  --sku Standard_LRS \
  --kind StorageV2 \
  --allow-blob-public-access false \
  --min-tls-version TLS1_2

2. Attach it to the log profile

If you are using a classic log profile, update it to reference the storage account and set a retention period:

az monitor log-profiles update \
  --name default \
  --storage-account-id "/subscriptions/<sub-id>/resourceGroups/security-logging-rg/providers/Microsoft.Storage/storageAccounts/lensixactivitylogs" \
  --set retentionPolicy.enabled=true retentionPolicy.days=365

If no log profile exists yet, create one:

az monitor log-profiles create \
  --name default \
  --location global \
  --locations eastus westus \
  --categories "Write" "Delete" "Action" \
  --storage-account-id "/subscriptions/<sub-id>/resourceGroups/security-logging-rg/providers/Microsoft.Storage/storageAccounts/lensixactivitylogs" \
  --days 365 \
  --enabled true

3. Or use the modern diagnostic setting

For new work, prefer a subscription-level diagnostic setting that exports the activity log to storage:

az monitor diagnostic-settings subscription create \
  --name activity-log-archive \
  --location eastus \
  --storage-account "/subscriptions/<sub-id>/resourceGroups/security-logging-rg/providers/Microsoft.Storage/storageAccounts/lensixactivitylogs" \
  --logs '[{"category":"Administrative","enabled":true},{"category":"Security","enabled":true},{"category":"Policy","enabled":true}]'

4. Lock the data down with immutability

Danger: Time-based immutability policies cannot be shortened or removed once locked. If you set a 365-day locked policy, the data is unmovable and undeletable for a year, and so is the cost. Test with an unlocked policy first, confirm the retention period is correct, then lock it.

# Set a time-based retention policy on the container (unlocked first)
az storage container immutability-policy create \
  --account-name lensixactivitylogs \
  --container-name insights-activity-logs \
  --period 365

# Once verified, lock it (irreversible)
az storage container immutability-policy lock \
  --account-name lensixactivitylogs \
  --container-name insights-activity-logs \
  --if-match "<etag>"

Warning: Archiving every category indefinitely has real storage costs, and immutable data cannot be deleted early to save money. Right-size your retention to what compliance actually requires, and use lifecycle management to move older blobs to the Cool or Archive tier.


How to prevent it from happening again

Fixing one subscription by hand does not stop the next one from launching without archival. Bake the configuration into your provisioning workflow.

Define it in Terraform

resource "azurerm_storage_account" "logs" {
  name                     = "lensixactivitylogs"
  resource_group_name      = azurerm_resource_group.logging.name
  location                 = "eastus"
  account_tier             = "Standard"
  account_replication_type = "LRS"
  min_tls_version          = "TLS1_2"
}

resource "azurerm_monitor_diagnostic_setting" "activity" {
  name               = "activity-log-archive"
  target_resource_id = "/subscriptions/${var.subscription_id}"
  storage_account_id = azurerm_storage_account.logs.id

  enabled_log {
    category = "Administrative"
  }
  enabled_log {
    category = "Security"
  }
  enabled_log {
    category = "Policy"
  }
}

Enforce it with Azure Policy

Use the built-in policy initiative Configure subscriptions to enable diagnostic settings to a storage account, assigned with a DeployIfNotExists effect. Any subscription that lands in scope without an archival target gets one deployed automatically through the policy's remediation task.

Tip: Assign the policy at the management group level rather than per subscription. New subscriptions created under that management group inherit the requirement on day one, so there is no manual onboarding step to forget.

Gate it in CI/CD

If you manage infrastructure as code, run a policy-as-code check before merge. Tools like Checkov or tfsec can fail a pull request when a subscription has no diagnostic setting or log profile wired to storage, so the gap never reaches production. Pair that with a recurring Lensix scan to catch drift introduced outside your pipelines.


Best practices

  • Separate the logging account from the workload. Keep archival storage in a dedicated subscription or resource group with tightly scoped access, so a compromise of a workload does not reach the audit trail.
  • Turn on immutable storage for any logs that back compliance claims. A retained log that can be edited is not really evidence.
  • Match retention to your strictest requirement. If PCI demands a year and SOC 2 demands more, default to the longer period rather than tracking two policies.
  • Send logs to more than one destination when it matters. Storage for cheap long-term archival, plus a Log Analytics workspace for querying and alerting, gives you both depth and speed.
  • Review access to the logging account regularly. Restrict delete and write permissions, and alert on changes to the storage account configuration itself.
  • Apply lifecycle rules to tier older blobs down to Cool or Archive, keeping retention high without paying hot-tier prices the whole way.

Activity log archival is one of those controls that costs almost nothing to set up and pays for itself the first time an investigation needs to look back further than 90 days. Wire it in once, enforce it with policy, and let the scans confirm it stays that way.