Create Custom Azure Policies
Learn how to create custom Azure Policy definitions to enforce governance, compliance, and security across your Azure resources. Step-by-step guide with hands-on exercise.
Focus: create custom azure policy definitions
You deployed a shiny new virtual machine in Azure, and within hours someone opened port 3389 to the world. Your compliance team sends you a stern email. How do you stop this from ever happening again? The answer isn't more manual checks — it's Azure Policy, a service that can automatically enforce rules across every resource in your subscription. In this lesson, you'll learn how to create custom Azure Policy definitions so you can enforce your own governance rules, beyond the built-in policies that Azure provides. You'll move from reactive firefighting to proactive, automated compliance.
The problem this lesson solves
Cloud environments grow fast. One developer creates a storage account with public blob access. Another spins up a database without encryption. Each mistake is a compliance gap, a security risk, and a headache for your team. You can't rely on manual audits or hope — you need a system that prevents misconfigurations the moment someone tries to create a resource.
Azure Policy is that system. It evaluates every resource against rules you define, and it can deny non-compliant resources, audit them, or even remediate them automatically. The built-in policies cover common scenarios, but every organization has unique requirements: maybe you want to ban certain VM sizes, require a specific tag on all resources, or force a naming convention that fits your company's standards.
That's where custom Azure Policy definitions come in. They let you turn your specific compliance requirements into code — a JSON document that Azure evaluates continuously. Without them, you're stuck with either generic policies or manual oversight.
Pro tip: Azure Policy is different from Azure RBAC. RBAC controls who can do what; Policy controls what can be created or configured, regardless of who does it.
Core concept / mental model
Think of Azure Policy as a security guard at the entrance of your Azure subscription. Every time someone requests to create or update a resource, the guard checks it against a rulebook. If the resource violates a rule, the guard either stops it (deny), flags it (audit), or fixes it automatically (remediate).
The guard's rulebook contains policy definitions. Each definition is a JSON document that describes:
- What resources it applies to (e.g., virtual machines, storage accounts)
- What condition to check (e.g., does the VM size equal Standard_DS1_v2?)
- What effect to apply (deny, audit, append, etc.)
You can bundle multiple definitions into a policy initiative (also called a policy set), and then assign either a definition or an initiative to a scope — a management group, subscription, or resource group.
The key mental model: Policy definitions are evaluation rules; assignments apply those rules to a scope. You can write a rule that says "deny any VM that is not tagged with 'CostCenter'" and assign it to your entire subscription. Every VM creation attempt is then evaluated against that rule, automatically.
How it works step by step
Let's walk through the anatomy of a custom policy definition. Here's a minimal example that denies storage accounts without HTTPS-only traffic enabled:
{
"mode": "All",
"policyRule": {
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Storage/storageAccounts"
},
{
"field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
"notEquals": true
}
]
},
"then": {
"effect": "deny"
}
}
}
Step 1: Set the mode. For most resource types, use "mode": "All". For Azure Resource Manager (ARM) properties that are evaluated on resource creation, you might use "mode": "Indexed" — but in practice, All is the safest default.
Step 2: Define the if block. This is the condition. You use field to reference resource properties, and operators like equals, notEquals, in, exists, and allOf/anyOf to combine checks.
Step 3: Define the then block. This is the effect. The most common effects are:
deny— blocks the resource creation or update.audit— allows the resource but marks it non-compliant in the Azure Policy dashboard.append— adds fields to the resource during creation (e.g., automatically adding a tag).deployIfNotExists— triggers a remediation task to fix existing resources.
Step 4: Package the definition in a JSON file that includes metadata, parameters, and the policy rule. Here's a fuller example with a parameter for allowed locations:
{
"mode": "All",
"parameters": {
"allowedLocations": {
"type": "array",
"metadata": {
"description": "The list of allowed Azure regions.",
"displayName": "Allowed locations"
}
}
},
"policyRule": {
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Resources/subscriptions/resourceGroups"
},
{
"field": "location",
"notIn": "[parameters('allowedLocations')]"
}
]
},
"then": {
"effect": "deny"
}
}
}
Step 5: Create the definition in Azure using the Azure CLI, PowerShell, or the portal. Then assign it to a scope and choose which effect to use (you can override the effect at assignment time if you've defined a parameter).
Pro tip: Always test a new policy with the
auditeffect first. That way, you can see which existing resources would be affected before you switch todenyand start blocking deployments.
Hands-on walkthrough
Let's put this into practice. You'll create a custom policy that denies virtual machines using the outdated Standard_A0 size (a classic compliance rule). We'll use Azure CLI, so make sure you're logged in (az login) and have an empty resource group where you can test.
Step 1: Write the policy definition file
Create a file named deny-vm-size.json with the following content:
{
"policyRule": {
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Compute/virtualMachines"
},
{
"field": "Microsoft.Compute/virtualMachines/sku.name",
"equals": "Standard_A0"
}
]
},
"then": {
"effect": "deny"
}
}
}
Step 2: Create the policy definition
az policy definition create \
--name deny-basic-vm-size \
--display-name "Deny Standard_A0 VM size" \
--description "Denies creation of VMs using Standard_A0 size." \
--rules deny-vm-size.json \
--mode All
Step 3: Assign the policy to a subscription (or resource group)
az policy assignment create \
--name deny-basic-vm-assignment \
--policy deny-basic-vm-size \
--scope /subscriptions/YOUR_SUBSCRIPTION_ID
Replace YOUR_SUBSCRIPTION_ID with your actual subscription ID.
Step 4: Test the policy
Try to create a VM with the Standard_A0 size:
az vm create \
--resource-group myResourceGroup \
--name test-vm \
--image UbuntuLTS \
--size Standard_A0
Expected output:
ERROR: Deployment failed. Correlation ID: ...
{
"error": {
"code": "RequestDisallowedByPolicy",
"message": "Resource 'test-vm' was disallowed by policy.",
"additionalInfo": [
{
"type": "PolicyViolation",
"info": {
"policyDefinitionDisplayName": "Deny Standard_A0 VM size"
}
}
]
}
}
The VM creation is blocked, and the error message tells you exactly which policy denied it. That's your guard in action.
Step 5: Check compliance in the portal
Navigate to Policy > Policy compliance in the Azure portal. You'll see your assignment and a list of resources that are non-compliant (if any exist). For a deny policy, you'll see Not started for evaluation, but you can also trigger an on-demand evaluation scan with:
az policy state trigger-scan --subscription YOUR_SUBSCRIPTION_ID
This is a great way to see the worst offenders before you flip to deny.
Compare options / when to choose what
Azure Policy isn't the only way to enforce governance. Here's how it stacks up against alternatives:
| Approach | Best for | How it works | Limitations |
|---|---|---|---|
| Azure Policy (custom definitions) | Complex, organization-specific rules | Evaluates resources continuously and can deny/audit/remediate | Requires JSON knowledge; takes time to write and test |
| Azure Blueprints | Packaging environments (policy + role + resources) | Orchestrates templates and assignments | Not for dynamic, after-the-fact enforcement |
| Terraform / IaC validation | Enforcing rules at deployment time (pre-flight) | Checks templates before apply | Doesn't protect during day-to-day operations |
| Azure Security Center | Built-in security recommendations | Uses built-in policies | Limited customization |
When to choose custom definitions: Your rule isn't covered by built-in policies, or you need a very specific combination of conditions. When not to: If a built-in policy exists, start there — it's maintained by Microsoft and less likely to have bugs.
Another consideration: policy initiatives vs. individual assignments. If you have multiple related definitions (e.g., a compliance standard), group them into an initiative so you can assign them together. Also, you can extend built-in policies with policySetDefinition using the portal's Policy as Code approach, but that's more advanced.
For comparison, here's how you'd write the same deny rule using Azure PowerShell:
$definition = @{
DisplayName = "Deny Standard_A0 VM size"
Description = "Denies creation of VMs using Standard_A0 size."
Policy = @{
if = @{
allOf = @(
@{ field = "type"; equals = "Microsoft.Compute/virtualMachines" },
@{ field = "Microsoft.Compute/virtualMachines/sku.name"; equals = "Standard_A0" }
)
}
then = @{ effect = "deny" }
}
Mode = "All"
}
New-AzPolicyDefinition -Name "deny-basic-vm-size" @definition
Choose Azure CLI if you're on Linux/macOS or embedding in CI/CD; choose PowerShell if you're in a Windows environment. Both achieve the same result.
Troubleshooting & edge cases
1. The policy doesn't deny anything
You expect a resource to be blocked, but it gets created. Check:
- Is the field name correct? Use az provider show --namespace <provider> to inspect resource properties, or use the Policy > JSON editor to pick fields.
- Is the policy assigned to the correct scope? If you assigned to a resource group, resources in other groups are unaffected.
- Did you create the definition with --mode All? Some fields only work in Indexed mode; if you used the wrong mode, the condition might never match.
2. The policy denies everything
Classic sign: your if block has a flaw. For example, if you used field: type with equals but the value is wrong, every resource matches. Start with an audit effect, then check the compliance report to confirm the condition is correct.
3. "RequestDisallowedByPolicy" errors are confusing
Read the error message carefully. It tells you which policy definition and assignment caused the block. If you're using a deny assignment, the error will be RequestDisallowedByPolicy. If it's from a deployIfNotExists assignment, you'll see a remediation task instead.
4. The policy applies only to new resources, not existing ones
Policy is evaluated on creation and update by default. If you want to remediate existing non-compliant resources, you need a deployIfNotExists effect and a remediation task. You can also use the azure policy state trigger-scan to run an on-demand evaluation, but that only evaluates — it doesn't change anything.
5. Fields with dots in the name
Azure Policy uses field references like Microsoft.Compute/virtualMachines/sku.name. If you get a parse error, make sure you're not using a top-level property like properties.sku.name — the field syntax is specific and case-sensitive.
What you learned & what's next
You've mastered the core of creating custom Azure Policy definitions. You can now:
- Explain the difference between policy definitions, assignments, and scope.
- Write a custom policy in JSON with an if/then rule and the right effect.
- Create and assign policies using Azure CLI, and test them in a sandbox environment.
- Choose between Azure Policy, Blueprints, and IaC validation based on your use case.
- Troubleshoot common errors like RequestDisallowedByPolicy and misconfigured fields.
Next in this Azure Tutorial track, you'll explore more advanced topics like policy initiatives and remediation tasks — automating the fix of non-compliant resources. Stay tuned!
Practice recap
Now try creating a custom policy that denies the creation of any storage account with supportsHttpsTrafficOnly set to false. Assign it to your sandbox resource group with the audit effect first, then switch to deny after verifying the compliance report. This exercise solidifies the pattern you just learned.
Common mistakes
- Using the wrong
mode— choosingIndexedwhenAllis needed leads to policies that never trigger on certain resource types. - Miswriting
fieldreferences — usingproperties.locationinstead oflocation, or mistyping resource provider paths likeMicrosoft.Compute/virtualMachines/sku.name. - Assigning the policy to a scope that's too narrow — assigning to a resource group when you meant to cover the whole subscription, or vice versa.
- Skipping the
auditphase — jumping straight todenywithout testing on existing resources can block critical deployments. - Forgetting that Policy only evaluates on create/update — existing non-compliant resources are ignored unless you set up remediation with
deployIfNotExists.
Variations
- Use
Azure PowerShell(New-AzPolicyDefinition) for Windows-centric automation or when embedding in Azure DevOps pipelines. - Deploy policy definitions as code using Terraform's
azurerm_policy_definitionresource, giving you version-controlled, reproducible governance. - Combine multiple custom definitions into an initiative (
policySetDefinition) so you can assign them together as a single compliance bundle.
Real-world use cases
- Enforce a tag like
CostCenteron every resource by using anappendeffect to add the tag automatically during creation. - Block creation of public network access on storage accounts using a deny policy to meet security compliance standards.
- Force all managed disks to use a minimum SKU (e.g., Premium_LRS) across a subscription for performance consistency.
Key takeaways
- Azure Policy uses JSON definitions with an
if/thenstructure to evaluate resources against your own compliance rules. - The
effectdetermines whether a policy denies, audits, or appends changes to resources — start withaudit, then move todeny. - Assignments apply a definition to a scope (management group, subscription, or resource group) — scope is everything.
- Field references must match Azure Policy syntax exactly, like
Microsoft.Compute/virtualMachines/sku.name, or the rule won't fire. - Policy is evaluated at resource creation/update; use
deployIfNotExistsand remediation tasks to fix existing non-compliant resources.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.