Home / Azure

AzureTroubleshootingAdvanced

RequestDisallowedByPolicy: find the Azure Policy that blocked your deployment and fix it properly

Decode a RequestDisallowedByPolicy error, find the exact assignment, definition and initiative member that denied it, check which field failed, and choose between fixing the template, an exemption or a policy change.

Deepak Bhardwaj4 min read

Applies toAzure Policy (deny, denyAction), ARM/Bicep/Terraform deployments, Azure CLI 2.x

Symptom

A deployment that worked in dev fails in production:

Code: RequestDisallowedByPolicy
Message: Resource 'stappprod01' was disallowed by policy. Reasons: 'Storage accounts should disable public network access'.
See error details for policy resource IDs.

Terraform shows the same as StatusCode=403 ... RequestDisallowedByPolicy. The short message is rarely enough. You need the assignment and the exact condition.

Step 1: get the full error details

The full error includes policyAssignmentId, policyDefinitionId and, for initiatives, policyDefinitionReferenceId.

az monitor activity-log list --resource-group rg-app-prod --offset 2h --status Failed \
  --query "[?contains(properties.statusCode || '', 'Forbidden') || contains(to_string(properties), 'RequestDisallowedByPolicy')].properties.statusMessage" -o tsv

For ARM/Bicep deployments it's quicker to read the deployment operation:

az deployment operation group list -g rg-app-prod -n main \
  --query "[?properties.provisioningState=='Failed'].properties.statusMessage.error.additionalInfo" -o json

Look for:

"policyAssignmentId": "/providers/Microsoft.Management/managementGroups/mg-landingzones/providers/Microsoft.Authorization/policyAssignments/Deny-Storage-Public",
"policyDefinitionReferenceId": "StoragePublicNetworkAccess",
"policyEffect": "deny"

Step 2: read the assignment and definition

az policy assignment show --ids "<policyAssignmentId>" \
  --query "{name:displayName, scope:scope, enforcement:enforcementMode, params:parameters, notScopes:notScopes}"

az policy definition show --name "<definition-guid-or-name>" --query "policyRule" -o json

For an initiative (policySetDefinitions), find the member by its reference ID:

az policy set-definition show --name "<initiative-name>" \
  --query "policyDefinitions[?policyDefinitionReferenceId=='StoragePublicNetworkAccess']"

Read the if block. It tells you exactly which property must be set. For example, publicNetworkAccess must equal Disabled.

Step 3: check what your template actually sends

The most common causes, in order:

  1. The template omits the property, and the API default violates the policy. Example: omitting publicNetworkAccess means "Enabled" on many resource types.
  2. The template sets an older apiVersion where the property doesn't exist, so it never reaches the policy engine.
  3. A child resource is created separately (a storage container, NSG rule, diagnostic setting) and the policy evaluates it on its own.
  4. A denyAction policy blocks a delete (for example, deleting a resource tagged protected).

Fix the template first. That is the right fix most of the time:

resource sa 'Microsoft.Storage/storageAccounts@2023-05-01' = {
  name: 'stappprod01'
  location: location
  sku: { name: 'Standard_ZRS' }
  kind: 'StorageV2'
  properties: {
    publicNetworkAccess: 'Disabled'
    allowBlobPublicAccess: false
    minimumTlsVersion: 'TLS1_2'
  }
}

Step 4: if the resource genuinely needs an exception, use an exemption

Don't edit the assignment's notScopes for one resource. Use a time-limited exemption scoped to the single resource and, for initiatives, the single policy:

az policy exemption create -n exm-stappprod01-public \
  --policy-assignment "<policyAssignmentId>" \
  --policy-definition-reference-ids StoragePublicNetworkAccess \
  --exemption-category Waiver \
  --scope "/subscriptions/<sub>/resourceGroups/rg-app-prod/providers/Microsoft.Storage/storageAccounts/stappprod01" \
  --expires-on 2027-01-31T00:00:00Z \
  --description "CHG-12345: vendor SFTP needs public endpoint until private link GA"

Warning

Exemption at resource scope only works once the resource exists. For a new resource that's blocked on create, exempt the resource group temporarily, deploy, then narrow the exemption to the resource.

Use Mitigated instead of Waiver when the risk is covered another way. The category shows up in compliance reports.

Step 5: test before production next time

Assign the policy with enforcementMode DoNotEnforce in a test scope. It evaluates and reports compliance but doesn't block:

az policy assignment create -n test-deny-public --scope "/subscriptions/<test-sub>" \
  --policy "<definition-id>" --enforcement-mode DoNotEnforce

Then list what would have been denied:

az policy state list --resource-group rg-app-test \
  --filter "ComplianceState eq 'NonCompliant'" \
  --query "[].{res:resourceId, policy:policyDefinitionReferenceId}" -o table

Troubleshooting table

Symptom Cause Fix
Works in dev, fails in prod Assignment at prod management group only Assign the same initiative to dev in DoNotEnforce
Property is set, still denied Old apiVersion doesn't send the property Update the apiVersion
Deny on a child resource you didn't expect Separate child resource evaluated Set the property on the child, or exempt at its scope
Delete blocked with denyAction Protected tag or resource type Remove the tag via change process, then delete
Exemption created, still denied Wrong assignment ID or reference ID; multiple assignments deny the same thing Check every assignment in the error details

Prevent it next time

  • Publish the landing zone policy list with the property each one requires, so app teams get it right in their templates.
  • Run az deployment group what-if and a DoNotEnforce assignment in CI to catch policy conflicts before release.
  • Track exemptions with expiry dates; review them monthly.
  • Related: Azure Policy remediation tasks.