Azure Policy is the mechanism that turns governance intentions into enforceable guardrails. Without it, every best practice in your cloud governance framework is a suggestion — documented in a wiki that developers read once during onboarding and never revisit. With it, non-compliant resources are either flagged, corrected, or blocked before they reach production.
The challenge is that Azure Policy is deceptively simple to start with and deceptively complex to operate at scale. A single “deny public IP” policy is easy enough. Managing hundreds of policy definitions across a management group hierarchy with exemptions, compliance reporting, and CI/CD pipelines requires deliberate architecture.
This guide covers Azure Policy best practices for 2026 — from foundational concepts to advanced patterns for policy-as-code, initiative management, exemptions, and compliance remediation. If you are building your landing zone from scratch, start with our Azure Landing Zones architecture guide first, since policy assignments are most effective when layered on top of a well-structured management group hierarchy. For broader governance strategy beyond Azure-specific tooling, our cloud governance best practices guide provides the organizational framework that Policy enforces technically.
How Azure Policy Works
Azure Policy evaluates resources against a set of rules and takes action based on the result. Every policy follows the same lifecycle: define, assign, evaluate, and remediate.
Policy Definitions
A policy definition is a JSON document that describes a condition and the effect to apply when that condition is met. Here is a minimal example that denies the creation of resources outside of allowed regions:
{
"properties": {
"displayName": "Allowed locations",
"policyType": "Custom",
"mode": "Indexed",
"parameters": {
"allowedLocations": {
"type": "Array",
"metadata": {
"displayName": "Allowed locations",
"description": "The list of locations that resources can be created in."
}
}
},
"policyRule": {
"if": {
"not": {
"field": "location",
"in": "[parameters('allowedLocations')]"
}
},
"then": {
"effect": "deny"
}
}
}
}
The mode field determines which resource types the policy evaluates. Indexed evaluates resources that support tags and location. All evaluates all resource types, including those that do not support tags. Use All for policies that target resource group properties or subscription-level configurations.
Policy Effects
Azure Policy supports several effects that control what happens when a resource matches a policy rule:
- Deny — Blocks the resource operation entirely. The deployment fails with an explicit error message.
- Audit — Logs a compliance event but allows the operation. Use this for visibility without enforcement.
- AuditIfNotExists — Checks whether a related resource exists (for example, a diagnostic setting) and logs non-compliance if it does not.
- DeployIfNotExists — Automatically deploys a remediation resource if it is missing. Requires a managed identity.
- Modify — Adds, updates, or removes properties on a resource during creation or update. Common for injecting tags.
- Append — Adds fields to a resource during creation or update. Being replaced by Modify in most use cases.
- Disabled — The policy is not evaluated. Useful for testing definitions without assigning them.
- Manual — Requires human attestation for compliance. Used for controls that cannot be automated.
Policy Assignments
A policy definition on its own does nothing. It becomes active only when it is assigned to a scope — a management group, subscription, resource group, or individual resource. Assignments bind a definition to a scope, supply parameter values, and configure enforcement behavior.
You can create an assignment using the Azure CLI:
az policy assignment create \
--name "allowed-locations" \
--display-name "Restrict resources to US regions" \
--policy "/providers/Microsoft.Authorization/policyDefinitions/<definition-id>" \
--scope "/providers/Microsoft.Management/managementGroups/Production" \
--params '{ "allowedLocations": { "value": ["eastus", "eastus2", "westus2", "centralus"] } }'
Key principle: Assign policies at the highest appropriate scope and use exclusions sparingly. Assigning at the management group level ensures all subscriptions under that group inherit the policy. Assigning at individual resource groups creates management overhead and gaps where new resource groups do not receive the policy.
Evaluation Cycle
Azure Policy evaluates compliance in two ways:
- On-demand evaluation — Triggered during resource creation, updates, and deletions. Deny policies block operations in real time.
- Background evaluation — Runs automatically every 24 hours and evaluates all existing resources against assigned policies. You can also trigger an on-demand evaluation scan using
az policy state trigger-scan.
# Trigger a compliance evaluation scan for a specific subscription
az policy state trigger-scan --subscription "your-subscription-id"
Background evaluation is what catches resources that were created before a policy was assigned. This is critical for understanding your compliance posture across existing infrastructure.
Built-in vs Custom Policies
Azure provides over 1,000 built-in policy definitions covering common governance scenarios. Before writing custom policies, check the built-in library — Microsoft maintains and updates these definitions as the platform evolves.
When to Use Built-in Policies
Built-in policies cover a wide range of scenarios:
- Allowed locations — Restrict resource creation to specific regions
- Allowed virtual machine SKUs — Limit VM sizes to control costs
- Require a tag and its value — Enforce tagging standards
- Storage accounts should use private endpoints — Enforce network security
- Kubernetes cluster pods should only use allowed images — Container security
- SQL databases should have vulnerability assessment configured — Database hardening
Built-in policies are preferable because they are tested, versioned by Microsoft, and updated automatically when Azure services change. They also integrate directly with Microsoft Defender for Cloud recommendations.
When to Write Custom Policies
Custom policies are necessary when your organization has governance requirements that built-in policies do not address:
- Enforcing organization-specific naming conventions
- Requiring specific tag keys with validation patterns (e.g., a CostCenter tag that matches a known format)
- Blocking specific resource types entirely (e.g., preventing teams from deploying classic resources)
- Enforcing subnet-level NSG association rules specific to your network architecture
- Requiring specific diagnostic settings configurations that go beyond what built-in policies check
Here is a custom policy definition that requires all resource groups to have an Owner tag:
{
"properties": {
"displayName": "Require Owner tag on resource groups",
"policyType": "Custom",
"mode": "All",
"parameters": {},
"policyRule": {
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Resources/subscriptions/resourceGroups"
},
{
"field": "tags['Owner']",
"exists": "false"
}
]
},
"then": {
"effect": "deny"
}
}
}
}
You can deploy this definition using Azure CLI:
az policy definition create \
--name "require-owner-tag-rg" \
--display-name "Require Owner tag on resource groups" \
--rules @require-owner-tag-rg.json \
--mode "All" \
--management-group "root-mg"
Best practice: Always define custom policies at the management group level, not the subscription level. This makes them available for assignment across the entire hierarchy.
Policy Initiatives (Sets)
Individual policy assignments become unmanageable at scale. If you have 30 policies to enforce, assigning them individually to 10 management groups creates 300 assignments to track. Policy initiatives (also called policy sets) group multiple policy definitions into a single assignable unit.
Creating an Initiative
An initiative bundles related policies together. For example, a “Baseline Security” initiative might include:
- Require encryption at rest for storage accounts
- Require HTTPS on storage accounts
- Require TLS 1.2 on App Services
- Require encryption in transit for SQL databases
- Deny public IP addresses on NICs
{
"properties": {
"displayName": "Baseline Security Initiative",
"policyType": "Custom",
"metadata": {
"category": "Security"
},
"parameters": {},
"policyDefinitions": [
{
"policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/b2982f36-99f2-4db5-8eff-283140c09693",
"policyDefinitionReferenceId": "storageEncryptionAtRest"
},
{
"policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/404c3081-a854-4457-ae30-26a93ef643f9",
"policyDefinitionReferenceId": "storageHttps"
},
{
"policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/f0e6e85b-9b9f-4a4b-b67b-f730d42f1b0b",
"policyDefinitionReferenceId": "appServiceTls"
}
]
}
}
Assign the initiative with a single command:
az policy assignment create \
--name "baseline-security" \
--display-name "Baseline Security Initiative" \
--policy-set-definition "baseline-security-initiative" \
--scope "/providers/Microsoft.Management/managementGroups/LandingZones"
Initiative Design Patterns
Organize initiatives by governance domain rather than by resource type:
- Security Baseline — Encryption, network isolation, endpoint protection
- Cost Governance — Allowed SKUs, required cost tags, budget thresholds
- Operational Excellence — Diagnostic settings, log routing, backup requirements
- Regulatory Compliance — Industry-specific controls (HIPAA, PCI-DSS, NIST 800-53)
This maps cleanly to how governance teams think about compliance. A security team audits the Security Baseline initiative; a finance team monitors the Cost Governance initiative. If you are working to align costs with governance, our guide on FinOps strategies covers the organizational processes that complement these technical controls.
Enforcement Modes
Not every policy assignment should block deployments on day one. Azure Policy supports two enforcement modes that control how strictly a policy is applied:
- Default (Enabled) — The policy effect is fully enforced. Deny policies block non-compliant deployments.
- DoNotEnforce — The policy evaluates resources for compliance but does not enforce the effect. Deny policies become audit-only.
DoNotEnforce is critical for rolling out policies safely. The recommended pattern:
- Deploy the policy in DoNotEnforce mode to see what existing resources are non-compliant
- Review the compliance results and work with resource owners to remediate
- Switch to Default enforcement once the non-compliance count is acceptable
# Assign a policy in DoNotEnforce mode (audit only)
az policy assignment create \
--name "deny-public-ip-audit" \
--display-name "Deny Public IPs (Audit Only)" \
--policy "/providers/Microsoft.Authorization/policyDefinitions/<definition-id>" \
--scope "/providers/Microsoft.Management/managementGroups/Production" \
--enforcement-mode "DoNotEnforce"
# Later, switch to full enforcement
az policy assignment update \
--name "deny-public-ip-audit" \
--enforcement-mode "Default"
Avoid the big-bang approach. Enabling dozens of deny policies simultaneously across production subscriptions will break deployments, frustrate development teams, and create immediate pressure to add exemptions that undermine the governance framework. Roll policies out incrementally — audit first, enforce later.
Policy Exemptions
Even the best policy framework requires exceptions. A legacy application might need a public IP. A partner integration might require a non-standard region. Azure Policy exemptions provide a structured way to handle these cases without disabling the policy entirely.
Exemption Categories
- Waiver — The resource does not meet the policy requirement, and the organization accepts the risk. Waivers should have an expiration date and a documented justification.
- Mitigated — The resource does not meet the policy requirement, but the risk is addressed through an alternative control (for example, a network-level firewall rule instead of a resource-level NSG).
az policy exemption create \
--name "legacy-app-public-ip" \
--policy-assignment "/subscriptions/<sub-id>/providers/Microsoft.Authorization/policyAssignments/deny-public-ip" \
--exemption-category "Waiver" \
--scope "/subscriptions/<sub-id>/resourceGroups/legacy-app-rg" \
--expires-on "2026-09-30" \
--description "Legacy app requires public IP for partner integration. Migration to Private Link planned for Q3 2026."
Exemption Best Practices
- Always set an expiration date on Waiver exemptions. Permanent exemptions become permanent security gaps.
- Scope exemptions as narrowly as possible. Exempt a specific resource group, not an entire subscription.
- Document the justification in the exemption description. Future auditors need to understand why the exemption exists.
- Review exemptions quarterly. Track them alongside your policy compliance dashboard.
- Require approval workflows for new exemptions. Use Azure DevOps or GitHub pull requests to gate exemption creation.
Compliance Dashboard and Reporting
The Azure Policy compliance dashboard in the Azure portal provides a visual overview of compliance across all scopes. It shows the percentage of compliant resources for each policy assignment and lets you drill down into individual non-compliant resources.
Querying Compliance Data Programmatically
For reporting beyond the portal, use Azure CLI or Azure Resource Graph queries:
# Get compliance summary for a specific policy assignment
az policy state summarize \
--policy-assignment "baseline-security" \
--management-group "LandingZones"
# List all non-compliant resources for a specific policy
az policy state list \
--filter "complianceState eq 'NonCompliant' and policyDefinitionName eq 'require-owner-tag-rg'" \
--management-group "LandingZones" \
--query "[].{Resource:resourceId, Policy:policyDefinitionName}" \
--output table
For integration with dashboards like Azure Cost Management or third-party reporting tools, export compliance data using Azure Resource Graph:
PolicyResources
| where type == "microsoft.policyinsights/policystates"
| where properties.complianceState == "NonCompliant"
| project
resourceId = properties.resourceId,
policyName = properties.policyDefinitionName,
resourceType = properties.resourceType,
timestamp = properties.timestamp
| order by timestamp desc
Setting Up Compliance Alerts
Proactive governance means knowing about non-compliance before your next audit review:
- Create an Azure Monitor alert rule that triggers when the compliance percentage drops below a threshold
- Route alerts to a Teams channel or email distribution list
- Integrate with your incident management workflow (ServiceNow, PagerDuty, etc.)
Policy as Code with CI/CD
Managing policies through the Azure portal works for a handful of assignments. At scale, you need the same version control, peer review, and automated deployment that you use for application code. This approach — often called “policy as code” — treats policy definitions, initiatives, and assignments as deployable artifacts stored in Git.
Repository Structure
A common repository layout for policy as code:
azure-policy/
definitions/
security/
deny-public-ip.json
require-encryption-at-rest.json
require-tls-12.json
cost/
allowed-vm-skus.json
require-cost-tags.json
operational/
require-diagnostic-settings.json
initiatives/
baseline-security.json
cost-governance.json
assignments/
management-groups/
production.json
non-production.json
sandbox.json
exemptions/
legacy-app-public-ip.json
tests/
policy-tests.sh
CI/CD Pipeline
Whether you use Azure DevOps, GitHub Actions, or another CI/CD tool, the pipeline follows the same pattern:
- Validate — Lint JSON, check for schema compliance, run policy tests
- Plan — Deploy in
DoNotEnforcemode to preview compliance impact - Deploy — Apply policy changes to the target scope
Here is a GitHub Actions workflow example:
name: Deploy Azure Policies
on:
push:
branches: [main]
paths: ['azure-policy/**']
pull_request:
branches: [main]
paths: ['azure-policy/**']
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate policy definitions
run: |
for file in azure-policy/definitions/**/*.json; do
echo "Validating $file"
jq empty "$file" || exit 1
done
deploy:
needs: validate
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Deploy policy definitions
run: |
for file in azure-policy/definitions/**/*.json; do
name=$(basename "$file" .json)
az policy definition create \
--name "$name" \
--rules "$file" \
--management-group "root-mg" \
--mode "Indexed"
done
If you are evaluating which IaC tool to use for your policy-as-code pipeline, our comparison of Terraform vs Bicep vs ARM Templates covers the trade-offs for managing Azure resources through code. Terraform’s azurerm_policy_definition and azurerm_policy_assignment resources provide full lifecycle management including state tracking and drift detection.
Common Governance Patterns
The following patterns represent the governance controls that most organizations should implement as a baseline. Adapt the parameter values to your organization’s requirements.
Required Tagging
Tags are the foundation of cost attribution, ownership tracking, and automated operations. Without enforced tagging, your cost management dashboard will show a growing percentage of untaggable spend.
Recommended minimum tags:
| Tag Key | Purpose | Example Value |
|---|---|---|
CostCenter | Cost allocation | CC-12345 |
Owner | Responsible team or individual | platform-team@company.com |
Environment | Deployment stage | Production, Staging, Development |
Application | Workload identifier | customer-portal |
Use the Modify effect to automatically inherit tags from the resource group when individual resources do not specify them:
{
"policyRule": {
"if": {
"allOf": [
{
"field": "tags['CostCenter']",
"exists": "false"
},
{
"value": "[resourceGroup().tags['CostCenter']]",
"notEquals": ""
}
]
},
"then": {
"effect": "modify",
"details": {
"roleDefinitionIds": [
"/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c"
],
"operations": [
{
"operation": "addOrReplace",
"field": "tags['CostCenter']",
"value": "[resourceGroup().tags['CostCenter']]"
}
]
}
}
}
}
Allowed Regions
Restrict resource creation to regions that comply with your data residency requirements and cost optimization strategy:
az policy assignment create \
--name "allowed-regions" \
--policy "/providers/Microsoft.Authorization/policyDefinitions/e56962a6-4747-49cd-b67b-bf8b01975c4c" \
--scope "/providers/Microsoft.Management/managementGroups/LandingZones" \
--params '{ "listOfAllowedLocations": { "value": ["eastus", "eastus2", "westus2"] } }'
Allowed VM SKUs
Prevent teams from deploying oversized or expensive VMs by restricting the allowed SKU list:
az policy assignment create \
--name "allowed-vm-skus" \
--policy "/providers/Microsoft.Authorization/policyDefinitions/cccc23c7-8427-4f53-ad12-b6a63eb452b3" \
--scope "/providers/Microsoft.Management/managementGroups/Production" \
--params '{ "listOfAllowedSKUs": { "value": ["Standard_D2s_v5", "Standard_D4s_v5", "Standard_D8s_v5", "Standard_E2s_v5", "Standard_E4s_v5"] } }'
This is one of the most effective cost governance controls available. Teams that need a larger SKU can request an exemption, which creates a natural approval gate.
Require Encryption
Enforce encryption across storage and database services:
- Storage accounts: Assign the built-in policy “Storage accounts should use customer-managed key for encryption” or at minimum ensure default Microsoft-managed encryption is not disabled.
- SQL databases: Assign “Transparent data encryption on SQL databases should be enabled.”
- Managed disks: Assign “Managed disks should use a specific set of disk encryption sets for the customer-managed key encryption.”
Deny Public Network Access
For production environments, deny public network access on services that support private endpoints:
- Storage accounts should disable public blob access
- Azure SQL should deny public network access
- Key Vault should disable public network access
- Azure Container Registry should not allow unrestricted network access
Group these into a “Network Isolation” initiative and assign it to your Production management group.
Troubleshooting Non-Compliant Resources
When the compliance dashboard shows non-compliant resources, follow a systematic remediation process.
Identify the Root Cause
# Get detailed compliance information for a specific resource
az policy state list \
--resource "/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.Storage/storageAccounts/<account>" \
--query "[?complianceState=='NonCompliant'].{Policy:policyDefinitionName, Reason:policyDefinitionAction}" \
--output table
Common Causes of Non-Compliance
- Pre-existing resources — Resources created before the policy was assigned. Remediation tasks can fix these for DeployIfNotExists and Modify policies.
- Exempted resources that expired — The exemption reached its expiration date and the resource is now evaluated normally.
- Deployment through non-standard channels — Resources created via direct API calls or legacy scripts that bypass organizational deployment pipelines.
- Policy evaluation delay — New resources may appear non-compliant for up to 30 minutes during the initial evaluation cycle.
Remediation Tasks
For policies with DeployIfNotExists or Modify effects, you can create remediation tasks that retroactively fix non-compliant resources:
# Create a remediation task for a specific policy assignment
az policy remediation create \
--name "remediate-diagnostic-settings" \
--policy-assignment "require-diagnostic-settings" \
--scope "/subscriptions/<sub-id>" \
--resource-discovery-mode "ReEvaluateCompliance"
Remediation tasks require a managed identity with the appropriate RBAC role. The role is specified in the policy definition’s roleDefinitionIds field. Always review what changes a remediation task will make before running it in production.
Troubleshooting Deny Failures
When a deployment fails due to a deny policy, the error message includes the policy definition name and assignment. Use this to identify which policy blocked the operation:
# List policy assignments at a specific scope
az policy assignment list \
--scope "/subscriptions/<sub-id>/resourceGroups/<rg>" \
--query "[].{Name:name, Policy:policyDefinitionId, Enforcement:enforcementMode}" \
--output table
If the deny is unexpected, check whether the policy was recently assigned, whether the resource is in the correct scope, and whether the policy parameters match the resource configuration.
Azure Policy Governance Checklist
Use this checklist to validate your Azure Policy implementation:
Foundation:
- Management group hierarchy designed and deployed
- Policy definitions stored in a Git repository
- CI/CD pipeline configured for policy deployment
- Policy naming convention documented and enforced
Policy Design:
- Built-in policies evaluated before writing custom definitions
- Custom policies defined at the management group level
- Initiatives organized by governance domain (security, cost, operations)
- Parameters used for flexibility rather than hardcoding values
Rollout:
- New policies deployed in DoNotEnforce mode first
- Compliance impact reviewed before switching to enforcement
- Resource owners notified before enforcement changes
- Rollback plan documented for each enforcement change
Exemptions:
- Exemption approval workflow defined
- All exemptions have expiration dates
- Exemptions scoped to the narrowest possible level
- Quarterly exemption review scheduled
Compliance:
- Compliance dashboard reviewed weekly
- Non-compliance alerts configured and routed
- Remediation tasks scheduled for DeployIfNotExists policies
- Compliance reports generated for audit cycles
Ongoing Operations:
- New Azure service releases reviewed for applicable policies
- Policy definitions updated when Microsoft releases new built-ins
- Policy effectiveness metrics tracked (non-compliance trending down)
- Team training on policy authoring and troubleshooting completed
Frequently Asked Questions
How many policies can I assign to a single scope?
Azure supports up to 500 policy or initiative assignments per scope. In practice, you should rarely approach this limit if you are using initiatives effectively. If you find yourself with hundreds of individual policy assignments, consolidate them into initiatives.
Do Azure Policies affect existing resources or only new ones?
Both. Deny policies only affect new resource creation and updates — they cannot delete or modify existing resources. However, DeployIfNotExists and Modify policies can remediate existing resources through remediation tasks. All policies evaluate existing resources during the background compliance scan, so non-compliant existing resources appear in the compliance dashboard regardless of the effect type.
What is the performance impact of Azure Policy on deployments?
Each Deny or Append/Modify policy adds evaluation time to resource deployments. For most environments, this is negligible (a few seconds). However, environments with hundreds of active deny policies may see noticeable delays on complex ARM deployments. Use initiatives to group related policies and avoid redundant evaluations.
Can I test policies before assigning them?
Yes. Use DoNotEnforce mode to assign a policy without enforcing its effect. The compliance dashboard will show which resources would be non-compliant without actually blocking any operations. You can also use the Azure Policy extension for VS Code to validate policy definitions locally.
How do Azure Policies interact with Terraform and Bicep deployments?
Azure Policies evaluate at the ARM layer, which means they apply to all resource deployments regardless of the tool used. A Terraform apply or Bicep deployment that creates a non-compliant resource will be blocked by a Deny policy just as a portal deployment would. If you manage your infrastructure with code, see our Terraform vs Bicep vs ARM Templates comparison for guidance on integrating policy evaluation into your IaC workflows.
What is the difference between Azure Policy and RBAC?
RBAC (Role-Based Access Control) controls who can perform actions. Azure Policy controls what can be done, regardless of who is doing it. A user with Contributor access can create any resource type — but an Azure Policy can restrict that user to specific regions, SKUs, or configurations. They are complementary controls: RBAC for identity and access, Azure Policy for resource configuration.
Next Steps
Azure Policy is the enforcement layer that makes cloud governance real. Without it, your governance framework is a document. With it, your governance framework is infrastructure. The investment in getting policy architecture right pays dividends in security posture, cost predictability, and audit readiness.
If your organization is building out its Azure governance foundation — or struggling with policy sprawl and compliance gaps in an existing environment — Exodata’s cloud engineering team can help you design a policy-as-code framework that scales with your organization. Talk to an engineer today to discuss your governance requirements.