Why this matters
Azure subscriptions start with a wide surface. A developer can open a storage firewall, attach a public IP, or grant Owner at subscription scope without asking anyone. Guardrails make the safe path the default. They do not replace identity or network design. They stop the small set of changes that create most of the exposure.
Azure Policy is the main tool. You assign definitions or initiatives at a management group, subscription, or resource group, and assignments inherit down. Deny stops a noncompliant resource at deployment. Audit records it and puts it in the compliance report. The split matters: deny is a hard stop, audit is a cleanup list.
Management groups are the part teams skip. If every subscription sits under the tenant root, you have one blunt scope. Build a small tree that matches how the business owns risk: platform, production, nonproduction, sandbox. Assign common rules high and tighter rules lower.
Before you start
Draw the management group tree first. Three to six groups is enough for most mid-sized companies. Do not mirror every team or app. Moving a management group later is possible, but policy and role assignments at the old scope do not move with it.
Pick the first guardrail set. Start with rules that are hard to argue with: block public blob access on storage accounts, require disk encryption, block inbound RDP and SSH from the internet, require a data classification tag, and restrict allowed regions. Add identity rules that fit Azure, such as blocking custom role creation outside a platform subscription and limiting Owner assignments.
Check permissions. Resource Policy Contributor at the target management group lets you create definitions and assignments. A deployIfNotExists or modify assignment needs a managed identity, and you need Owner or User Access Administrator to grant it a role. Use a pipeline identity for policy deployments so the work is repeatable.
How to do it
Work from high to low and start in audit. The first pass should show what exists, not block a release. Once the report is clean enough, switch the few rules that must be hard stops to deny. Keep the rest as audit until the backlog is small.
Use initiatives for related rules. Give each custom initiative a clear name, a version, and a parameter file. Assign it at a management group, and exclude sandbox only if you have a real reason. Set a description and an owner on every assignment. That description is what the next engineer reads at 2 a.m.
Treat policy as code after the first pass. Export definitions and assignments, keep them in Git, and deploy with Bicep or Terraform through a pipeline. Use a plan to review scope and parameter changes. You get review, rollback, and a history the portal does not show.
- Map management groups to ownership. Create management groups for platform, production, nonproduction, and sandbox, then move subscriptions under them. Keep the tree shallow and name each group after the business owner, not a person. Check that every subscription has a parent before you assign policy.
- Pick a baseline initiative. Start with a built-in initiative for a common standard, such as CIS Azure Foundations. Remove controls for services you do not use. Give the initiative a version and a short description.
- Assign in audit first. Assign the initiative at the highest management group that should inherit it. Set the effect to Audit for the first pass. Do not assign at the tenant root unless you are sure every subscription, including sandbox, should inherit.
- Set deny for a short list. Choose five to ten rules that must never pass: public blob access, open management ports, unencrypted disks, and resources outside approved regions. Set those to Deny. Keep the list short, because every deny needs a clear error message and an exception path.
- Add exceptions with expiry. Use policy exemptions for real exceptions, not by editing the assignment. Scope the exemption to the resource or resource group, add a reason, and set an expiration date. Review expiring exemptions each month and remove the ones that no longer have a reason.
- Remediate existing resources. For deployIfNotExists and modify policies, create remediation tasks after the assignment. Check the managed identity role and the task status. Remediation can change live resources, so run it in nonproduction first and schedule production windows.
- Put policy in a pipeline. Export policy definitions, initiatives, assignments, and exemptions to a Git repository. Deploy them with Bicep or Terraform and run a plan on every pull request. Require a second reviewer for scope changes and deny effects.
- Review compliance on a schedule. Open Azure Policy compliance weekly and work the noncompliant resources by policy, not by subscription. Assign an owner to each high risk policy. Report the same small set of metrics to leadership so the list does not grow silently.
How to check it worked
Open Azure Policy and go to Compliance. Filter by the management group where you assigned the initiative. Check the assignment scope and the compliance state for each policy. Click a policy to see noncompliant resources and the reason. A policy with no resources is not proof. Confirm the scope includes the subscriptions you expect.
Test a deny with a real deployment. Try to create a storage account with public blob access enabled in a covered subscription. The deployment should fail with the policy name and reason. Then create the same resource in an exempted scope and confirm it passes. If both pass or both fail, the scope or exemption is wrong.
Use Azure Resource Graph to list policy states and exemptions. Query the PolicyResources table for the assignment name and group counts by subscription. Check remediation task status and exemption expiration dates. A clean report with ten permanent exemptions is not clean.
Mistakes to avoid
Starting with deny across the tenant is the first mistake. Teams get blocked on a Friday release, then someone removes the assignment. Start with audit, fix the backlog, and add deny one rule at a time with a named owner.
Assigning too many policies is the second. A large initiative with hundreds of rules creates noise and a compliance report nobody reads. Keep the organization-wide set to rules that matter for every subscription. Put service-specific rules in a lower management group or a separate initiative.
Using exemptions as a permanent escape hatch is the third. An exemption without an expiry is a policy change in disguise. Require a reason, a ticket, and a date. Review the list with access reviews.
Ignoring the managed identity on deployIfNotExists and modify assignments is the fourth. The assignment looks healthy, but remediation fails because the identity lacks the role. Check the identity permissions at assignment time and after scope changes. If you want an outside read before you build, the Cloud security review from Cloudverg is $6,500/month, fixed scope, and gives you a ranked fix list. Other teams start with an internal workshop and a pilot subscription.
Frequently asked questions
What is the difference between Azure Policy and Microsoft Defender for Cloud?
Azure Policy sets and enforces rules on resources at a scope. Defender for Cloud finds threats and misconfigurations and shows regulatory compliance, but it does not block a deployment by itself. Use Policy for guardrails and Defender for Cloud for detection and posture.
Should I use deny or audit first in Azure Policy?
Audit first for most rules. It shows what exists without breaking releases. Move a rule to deny only after the backlog is small, the error message is clear, and you have an exemption process.
Do Azure Policy assignments apply to existing resources?
Audit and deny both evaluate existing resources for compliance, but deny only stops new or updated deployments. To change existing resources, use a deployIfNotExists or modify policy and run a remediation task.