Skip to content

Firewall Rule and IP Group Guidelines

The runbook Firewall rules explains how to add, change or remove a rule: the files, the JSON and the pipeline. This page explains how to design rules and IP groups so that the configuration stays readable, stays within the Azure limits and can be handled by more than one team.

Firewall rules are code. Don't create or change rules in the portal: the next deployment of the firewall policy overwrites everything that was not made through the configuration repository.

The structure of the files and the checks of the config tests are fixed by the platform. The naming and numbering schemes on this page are conventions. Two customer platforms use different ones. Choose one scheme for your platform, write it down and apply it consistently. Where this page gives numbers, they are examples.

How Azure Firewall organises rules

A firewall policy contains rule collection groups (RCG). An RCG contains rule collections. A rule collection contains rules.

Level What it is Priority
Rule collection group The first unit the firewall processes. One JSON file in the configuration repository. 100 (processed first) to 65000. Must be unique in the policy.
Rule collection Has one type (DNAT, network or application) and one action (Allow or Deny). Unique inside its RCG.
Rule Matches source, destination, port and protocol. No priority.

The firewall denies everything that no rule allows. Use Allow in nearly every collection. Use Deny only for specific security needs.

Processing order. Azure Firewall processes the RCGs in priority order, lowest number first. Inside one RCG it processes DNAT collections first, then network collections, then application collections, each by collection priority. That is why the config test requires the lowest NAT priority to be lower than the lowest network priority, and that one lower than the lowest application priority. See Microsoft's rule processing logic.

Rule types:

Type Use it for
DNAT Inbound traffic to a firewall public IP, translated to a private address. See DNAT rules.
Network Layer 3 and 4: IP addresses, ports, protocols. Inbound, outbound and traffic between spokes.
Application Layer 7: FQDNs and HTTP, HTTPS or SQL protocols, mainly for outbound traffic.

Network rules need fewer resources than application rules. Use a network rule when you don't need FQDN matching.

Which type? Use an application rule when the request has only HTTP (80), HTTPS (443) or SQL traffic and the destination is an FQDN. Use a network rule in every other case. The runbook has the checklist: Application or network rule.

Do not let rules overlap. The first match decides, and a deny that matches first hides the allow that follows. A network rule that denies HTTPS to an FQDN wins over an application rule that allows the same FQDN, when it is processed first. Check new rules against existing ones, especially across collection groups.

Azure limits to design for

These limits apply to a firewall policy at the time of writing. Check Microsoft's Azure Firewall limits for the current numbers.

Limit Value
Rule collection groups per policy 90
Size of one rule collection group 2 MB
Unique IP groups per policy 600
Addresses or prefixes in one IP group 5,000

Plan with these in mind. A large platform with many workloads can reach 90 RCGs. Don't create an RCG only to organise rules visually. Use rule collections for that.

When to create a new rule collection group

Create a separate RCG when:

  • the rules need a different priority tier (platform rules, production workloads, non-production workloads);
  • a different team owns them, so each team works in its own file;
  • they have a different lifecycle, for example temporary rules for a project;
  • a logical separation is required, for example a security zone or a compliance boundary;
  • the RCG gets close to the 2 MB limit.

Don't create a new RCG when the rules fit in an existing collection, when a collection priority is enough, or when the platform is already close to 90 RCGs.

Organise the configuration around workloads

Organise rules per workload, not per technology. A rule collection group called AllowDatabase mixes the traffic of many workloads. Nobody knows which rule belongs to which workload, rules conflict or repeat, and nobody can tell what to remove when a workload is decommissioned. One RCG file per workload (or per workload and environment type) fixes this.

A typical layout inside firewall_rule_configuration/ (sub-folders are allowed, see Firewall rules):

firewall_rule_configuration/
├── 0.platform/        Rules the platform team manages: DNS, updates, monitoring, managed VMs
├── 1.global/          Rules of the customer that apply to every workload
├── <workload-a>/      One folder per workload
│   ├── <workload-a>-prod.json
│   └── <workload-a>-non-prod.json
└── <workload-b>/

Some points about this layout:

  • Global and regional files. Put a rule in the global folder when it applies to every region, for example DNS: it must work everywhere. Put it in a region folder only when there is a real regional reason, such as a failover or residency requirement, or when it connects two spokes in one region. A regional file with the same name as a global file replaces it in that region. Keep rules consistent across regions where the regions back each other up.
  • Production and non-production apart. Use separate RCGs (or at least separate collections) for production and non-production traffic of a workload, so that production data can't flow to a non-production environment by accident. Don't allow traffic between the two.
  • Internal and internet apart. Keep internal rules and internet rules in separate collections. Internet traffic is the higher threat and deserves its own review.
  • Don't widen a rule for one source. If only one of several source IP groups needs access to a destination, do not add the destination to the existing rule or collection: that opens it for the other groups too. Add a separate rule.
  • One owner per file. The team that owns the workload owns the file. For traffic between two workloads, the rule lives in the file of the destination workload (who requests which rule).

Priorities

Azure requires a unique RCG priority per policy. The config test only checks uniqueness inside one firewall_rule_configuration folder, so also check global files against regional files yourself. Don't use 8999: the platform uses that priority for the deployment agents RCG when deployment_agents_private_network_range is set.

Give every owner a range, so that teams don't collide and priorities show what a rule is for. Leave gaps (for example steps of 10) so that you can insert rules later.

Example priority scheme

This is an example. Adapt it, and document your own.

Purpose RCG priority range
Rules managed by the platform team 1000-1099
Global rules of the customer 1100-1199
Production workloads 2000-2999
Non-production workloads 3000-5999
Reserved 6000-9999

Within the production and non-production ranges, each workload gets its own sub-range (for example 20 numbers). A workload's production range and non-production range use the same offset (2100-2119 and 3100-3119), which makes the pairs easy to find.

Collection priorities only need to be unique inside the RCG, and can repeat in other RCGs. Keep the same spacing in each file.

Naming

Names are identifiers in Azure and Terraform keys. Choose names that say whose rule it is and what it allows. Use only lowercase letters, numbers and hyphens (or underscores), keep them short, and avoid names such as rule1, misc, temp-all or allow-all. The config test accepts the pattern ^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9_]$ for collection and rule names.

Object Pattern (example) Examples
RCG <workload>-<project or service>-<environment type>. Platform-owned: <owner>-global-<description>. app1-prod, app1-reporting-non-prod, platform-global-dns
Rule collection A fixed name per type, or a name that shows type and environment NetworkRules, ApplicationRules
Rule <source>-to-<destination>, with extra detail when needed app1-vm-to-servicebus, appgw-to-app1-web, onprem-sql-to-app1-sql
IP group ipgrp-<description> (global), ipgrp-<workload>-<description>-<environment> (workload). An alternative that shows where the addresses live: ipgrp-<onprem or azure>-<workload>-<environment>-<use>. ipgrp-hub, ipgrp-app1-web-prod, ipgrp-onprem-dns

The RCG name must be unique across all files of a region, because it is the Azure name and the Terraform key. Renaming an RCG destroys and recreates it (see Change or remove an existing rule).

Design rules well

  • Use the most specific destination. In order of preference: an FQDN, a single IP address, an IP group, a subnet. Prefer the narrowest scope that works.
  • Internet destinations as FQDNs, not as IP addresses. Public services (operating system updates, vendor APIs) and third-party applications can change address. A rule on an address then stops matching without warning. An FQDN or an FQDN tag keeps working.
  • Use IP groups for address sets that more than one rule uses or that change often (see below).
  • Use service tags for Azure services in network rules (for example AzureBackup, AzureMonitor). Microsoft maintains the address ranges, and this performs better than FQDNs.
  • Use FQDN tags for Microsoft services in application rules (for example WindowsUpdate). The list of supported values is in the Firewall rules runbook.
  • One source type and one destination type per rule. Use either source_addresses or source_ip_groups, and either destination_addresses, destination_ip_groups or destination_fqdns. Split into two rules if you need both. The config test enforces this.
  • Consolidate. Combine rules with the same source, destination and action, and list several ports in one rule (["80", "443"]) instead of making one rule per port.
  • Least privilege. Name the exact ports and protocols. Avoid * as source or destination and avoid the Any protocol. Use wildcard FQDNs (*.example.com) with care.
  • Encrypted protocols by default. Many organisations only allow encrypted protocols and require an extra approval for unencrypted ones.
  • Remove what is no longer used. Azure Firewall Policy Analytics helps to find unused rules and to tune the order of rules.

An explicit deny rule

Add a Deny collection only for a specific security need, for example to block a sensitive subnet from the internet. Give the deny collection a lower priority number than the allow rules it must override, put it in a platform-owned RCG, and make sure that rule is reviewed by security.

IP groups

An IP group is a named set of addresses and ranges. Rules refer to it by name. The runbook with the steps and the JSON is IP groups. When a subnet changes, you update one file and every rule that uses the group follows. The name also tells the reader what the addresses are, and it is easy to see which networks a rule opens.

When to use an IP group

  • Several rules use the same set of addresses.
  • The addresses are shared infrastructure that many workloads refer to (hub networks, identity servers, management networks, the Application Gateway subnet).
  • The addresses change regularly.
  • The set has one logical purpose (the workstations of one workload in production, the Databricks subnets of one workload).
  • It is a workload or spoke. A good habit is to give every new spoke an IP group for its address range from the start, so that later rules can refer to it. Before you add a new rule, check whether an existing rule with the same purpose can get the new address through the group.

When not to use an IP group

  • The addresses are used by one rule only. Put them inline.
  • The rule is temporary or a one-off test.
  • The ruleset is very small.
  • The addresses are unrelated. Don't mix unrelated subnets in one group.

Where the files are and what they look like

IP group files live in ip_groups/ (see Firewall rules). One file holds one IP group. Use the same folder structure as for the rules: global groups in a platform or global folder, workload groups in a folder per workload, and the file name equal to the group name. A group that doesn't follow the naming convention can wait in a separate "uncategorized" folder until it is renamed.

{
  "ip_group": {
    "name": "ipgrp-app1-web-prod",
    "cidrs": ["10.20.4.0/26"]
  }
}

Write a single host as /32. The schema accepts a plain IPv4 address and an address with a prefix length from /0 to /32. Use one style within a group, and validate the file (Edit the JSON files with schema support).

Rename or remove an IP group in two steps

A rule that refers to a group that no longer exists fails the deployment. Never rename or remove a group in the same change that updates the rules.

Rename:

  1. Add a new file with the new name and the same addresses. Update every rule to use the new name. Deploy.
  2. When that is deployed in every environment, delete the old file. Deploy again.

Remove:

  1. Remove all references from the rules. Deploy.
  2. Delete the file. Deploy again.

Make firewall changes safely

  • One RCG change per pull request. A firewall policy update takes several minutes, and Azure doesn't support parallel updates of the same policy. Small pull requests are easier to review, apply and roll back.
  • Add, verify, remove. When a rule replaces another, add the new rule first, check that the traffic works, and remove the old rule in a separate pull request. Never remove a rule before its replacement is confirmed. Combining add and remove in one pull request can cause an outage.
  • Change every environment folder that needs it. The canary and prod folders are separate. Make the same change in both (canary first), unless the rule only applies to one.
  • Run the config tests before you push (Run the tests locally).
  • Check the plan. Only the firewall layer should show changes, and only for the RCG you touched.

If a large change times out

Several rule collection groups in one change can take longer than the apply time-out, because Azure queues the updates. When the run is aborted, some collections may exist in Azure but not in the Terraform state. The next run then fails because the resource already exists.

Don't destroy and recreate production rules to fix a state mismatch

Destroying the collections that already exist interrupts traffic for as long as the repair takes. Use terraform import for the resources that exist in Azure but not in the state. Better still, prevent it: when a canary run already shows time-outs, don't continue to production. Split the change into smaller pull requests, and apply new rules before removing the rules they replace.

Read the firewall logs

Firewall diagnostics can send logs to a Log Analytics workspace. The tables you will use most are:

Table Content
AZFWApplicationRule Application rule matches
AZFWNetworkRule Network rule matches
AZFWNatRule DNAT rule matches
AZFWDnsQuery DNS queries handled by the DNS proxy
AZFWFlowTrace, AZFWFatFlow Flow traces and the largest flows
AZFWIdpsSignature Intrusion detection signatures (Premium)

There are also aggregation tables (AZFWApplicationRuleAggregation, AZFWNetworkRuleAggregation, AZFWNatRuleAggregation). Give the people who troubleshoot rules read access to these tables and to the firewall resource, and not to the whole workspace. Verify which tables your diagnostic settings fill.