Back to blog
AzureBest PracticesCloud SecurityMonitoring & LoggingOperations & Compliance

Azure Monitor Not Archiving Critical Activity Categories: Why It Matters and How to Fix It

Learn why an Azure Monitor log profile that skips critical activity log categories breaks your audit trail, and how to fix and prevent it with CLI, Terraform, and policy.

TL;DR

This check flags Azure Monitor log profiles that fail to archive every critical activity log category. Without full coverage you lose the audit trail needed for incident response and compliance. Fix it by updating the log profile so all categories (Write, Delete, Action) are captured and shipped to a storage account or Log Analytics.

Azure records control-plane operations in the activity log: who created a virtual machine, who deleted a network security group rule, who rotated a key. That stream is one of the most valuable forensic resources you have when something goes wrong. But the activity log is only useful if it survives long enough to be read. By default, activity log entries are retained for 90 days and then discarded. If you have not configured a log profile or diagnostic setting to archive them, you are betting that you will catch every problem inside a three-month window. Attackers and auditors both tend to show up later than that.

The monitor_noarchive check inspects your Azure Monitor configuration and reports when a log profile is not archiving all of the critical activity log categories. This post covers what that means, why a partial configuration is almost as dangerous as no configuration, and how to close the gap for good.


What this check detects

Azure exposes activity log data across several categories. The three that matter most for security and audit are:

  • Write — create and update operations on resources (PUT, PATCH).
  • Delete — resource deletion operations.
  • Action — operations that are neither pure writes nor deletes, such as listing keys, restarting a VM, or invoking a custom action on a resource.

A log profile (or, in the modern model, a diagnostic setting on the activity log) controls which categories get exported and where they go. The monitor_noarchive check fails when a log profile exists but does not include all of the critical categories. A profile that captures Write and Delete but omits Action will pass a naive "is logging enabled?" test while quietly dropping a class of events that often matters most during an investigation.

Note: Microsoft is migrating from the older log profile API to diagnostic settings for the activity log. Both control the same export behavior. If your subscription still uses a log profile, this check evaluates its categories array. If you have moved to diagnostic settings, the equivalent is the set of enabled category groups on the activity log diagnostic setting.


Why it matters

The risk here is not theoretical. Consider how a typical Azure compromise unfolds:

  1. An attacker gains access to a service principal or user credential.
  2. They enumerate resources and pull secrets, often using Action operations like listKeys on a storage account or Microsoft.KeyVault/vaults/secrets/read.
  3. They make changes, then attempt to cover their tracks.

If your log profile skips the Action category, the listKeys call that exfiltrated your storage account keys never lands in long-term storage. You retain a record of the create and delete operations but lose the read-and-list activity that shows what data the attacker actually touched. During an incident review, that gap is the difference between a confident "they accessed these three secrets" and a vague "we cannot rule out broader exposure."

There is a compliance dimension too. Frameworks like CIS Azure Foundations, PCI DSS, and SOC 2 expect a complete and tamper-resistant audit trail of administrative actions. A log profile missing categories will fail those control mappings, and partial coverage is hard to defend in an audit because it looks like a deliberate decision to log less.

Warning: Activity log retention beyond the built-in 90 days requires exporting to a storage account, Log Analytics workspace, or event hub. Each of those has its own cost. Storage account archival is cheap, but Log Analytics ingestion is billed per GB. Budget for it before you turn on full-category export across many subscriptions.


How to fix it

The goal is a single export configuration that captures Write, Delete, and Action and sends them to durable storage. Below are three ways to do it, depending on your tooling.

Option 1: Azure CLI (diagnostic setting, recommended)

Create a diagnostic setting on the subscription activity log that ships all category groups to a storage account. First, find or create the destination storage account, then attach the setting:

SUBSCRIPTION_ID=$(az account show --query id -o tsv)
STORAGE_ID="/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/security-logs/providers/Microsoft.Storage/storageAccounts/activitylogarchive"

az monitor diagnostic-settings subscription create \
  --name "activity-log-archive" \
  --location global \
  --storage-account "$STORAGE_ID" \
  --logs '[
    {"category": "Administrative", "enabled": true},
    {"category": "Security", "enabled": true},
    {"category": "Alert", "enabled": true},
    {"category": "Policy", "enabled": true},
    {"category": "Recommendation", "enabled": true}
  ]'

The Administrative category group in diagnostic settings covers the Write, Delete, and Action operations that the legacy log profile referenced individually. Including Security and Policy rounds out the coverage most audits expect.

Option 2: Legacy log profile

If your environment still uses a log profile and you want to repair it in place rather than migrate, recreate it with all categories present:

Danger: Azure allows only one log profile per subscription. The command below deletes the existing profile before creating the replacement. There is a brief window where no profile is active, and any export gap means activity during that window is only retained for the default 90 days. Run this during a maintenance window and confirm the new profile is active immediately afterward.

# Remove the incomplete profile
az monitor log-profiles delete --name default

# Recreate with all categories and full retention
az monitor log-profiles create \
  --name default \
  --location global \
  --categories Write Delete Action \
  --storage-account-id "$STORAGE_ID" \
  --days 365 \
  --enabled true \
  --locations global eastus westus

Option 3: Terraform

For teams managing Azure with infrastructure as code, define the activity log diagnostic setting so it is enforced on every apply:

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

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

After applying, verify the setting took effect:

az monitor diagnostic-settings subscription list \
  --query "value[].{name:name, logs:logs[?enabled].category}" -o table

Tip: Send activity logs to both a storage account (cheap, immutable, long retention) and a Log Analytics workspace (queryable, alertable). Storage gives you the compliance archive; Log Analytics gives you the ability to run KQL queries and fire alerts on suspicious operations like mass deletes.


How to prevent it from happening again

A one-time fix decays. New subscriptions get created, someone disables a category to reduce ingestion cost, and you drift back into a failing state. Lock the configuration in with policy and pipeline controls.

Azure Policy with DeployIfNotExists

Azure ships a built-in policy that deploys an activity log diagnostic setting wherever one is missing. Assign it at the management group level so every current and future subscription inherits it:

az policy assignment create \
  --name "deploy-activity-log-archive" \
  --display-name "Archive all activity log categories" \
  --policy "/providers/Microsoft.Authorization/policyDefinitions/2d6830fb-f-..." \
  --scope "/providers/Microsoft.Management/managementGroups/root" \
  --location eastus \
  --mi-system-assigned \
  --role "Contributor"

The DeployIfNotExists effect means non-compliant subscriptions are remediated automatically rather than just flagged. Pair it with a remediation task to fix existing subscriptions on first assignment.

CI/CD gate for Terraform

If you provision subscriptions through Terraform, add a check to your pipeline that fails the plan when an activity log diagnostic setting is absent or missing categories. A lightweight approach uses Conftest with an OPA policy:

terraform show -json plan.tfplan | conftest test --policy policy/ -

The OPA rule asserts that any plan must contain an azurerm_monitor_diagnostic_setting on the subscription with the Administrative category enabled. A pull request that removes it cannot merge.

Tip: Lensix runs the monitor_noarchive check on a schedule, so even if a policy assignment is removed or a setting drifts outside of Terraform, you get an alert rather than discovering the gap during an incident. Continuous scanning catches the failure modes that point-in-time gates miss.


Best practices

  • Archive at the management group level. Configuring per-subscription is fragile. Apply diagnostic settings and policy at the root or a security management group so coverage is inherited automatically.
  • Use a dedicated, locked-down logging subscription. Send activity log archives to a storage account in a separate subscription with restricted access. This limits the blast radius if a workload subscription is compromised, and stops an attacker who gains access to one environment from deleting their own audit trail.
  • Enable immutable storage on the archive. Apply a time-based immutability policy to the storage container so logs cannot be altered or deleted before their retention period ends, even by an administrator.
  • Set retention to match your obligations. Ninety days is rarely enough. One year is a common baseline; regulated environments often require longer. Configure retention explicitly rather than relying on defaults.
  • Alert on the gaps that matter. Beyond archiving, build Log Analytics alerts for high-signal Action operations such as listKeys, key vault secret reads, and role assignment changes. Archiving preserves evidence, but alerting is what lets you respond in time.

Activity logging is the kind of control that costs almost nothing to get right and a great deal to get wrong. A complete, immutable, centrally enforced archive of every administrative action is the foundation your incident response and compliance posture both stand on. Fix the partial configurations now, enforce the full set of categories with policy, and let continuous scanning keep it that way.