Standing up Azure Firewall is easy. Running it well is a different story. The difference shows up in three places: whether your rule base is structured or a flat pile of exceptions, whether you actually inspect encrypted traffic, and whether IDPS is tuned or just generating noise nobody reads. This post covers all three based on production deployments.
Policy Hierarchy First
Azure Firewall separates the firewall resource from the firewall policy. Policies support inheritance: a base policy holds organization wide rules (deny known bad, allow core infrastructure like Entra ID, Windows Update, monitoring agents), and child policies per region or environment add local rules. Rules in the parent always evaluate first and cannot be overridden by children, which is exactly what a security team wants.
resource "azurerm_firewall_policy" "base" {
name = "afwp-base"
resource_group_name = azurerm_resource_group.sec.name
location = "westeurope"
sku = "Premium"
dns {
proxy_enabled = true
}
intrusion_detection {
mode = "Alert"
}
}
resource "azurerm_firewall_policy" "prod_weu" {
name = "afwp-prod-weu"
resource_group_name = azurerm_resource_group.sec.name
location = "westeurope"
sku = "Premium"
base_policy_id = azurerm_firewall_policy.base.id
threat_intelligence_mode = "Deny"
}
Inside each policy, use rule collection groups with deliberate priorities: platform allow rules at 10000, application rules per workload at 20000 and up, and an explicit logging deny near the bottom. Name collections after the workload and ticket that requested them, future you will be grateful during audits.
resource "azurerm_firewall_policy_rule_collection_group" "app_payments" {
name = "rcg-payments"
firewall_policy_id = azurerm_firewall_policy.prod_weu.id
priority = 20100
application_rule_collection {
name = "payments-egress"
priority = 100
action = "Allow"
rule {
name = "psp-api"
source_addresses = ["10.20.4.0/24"]
destination_fqdns = ["api.stripe.com", "api.adyen.com"]
terminate_tls = true
protocols {
type = "Https"
port = 443
}
}
}
}
TLS Inspection Without the Pain
Premium can terminate outbound TLS, inspect the payload, and re-encrypt toward the destination. It needs an intermediate CA certificate stored in Key Vault, referenced by the policy through a managed identity. The firewall then mints a certificate per destination on the fly, so every client in your estate must trust that intermediate CA, distributed through group policy, Intune, or baked into your golden images and container base layers.
Two practical warnings. Never inspect traffic to services that use certificate pinning, which includes a lot of payment and identity SDKs, so maintain an explicit no inspect list. And roll inspection out per workload with terminate_tls on individual rules rather than globally, starting with plain web egress where breakage is easy to spot.
Tuning IDPS
IDPS ships with tens of thousands of signatures. Run it in Alert mode for at least two weeks, query the AZFWIdpsSignature logs in Log Analytics, and identify the noisy signatures that fire on legitimate traffic in your environment. Then switch to Alert and Deny with specific signature overrides set back to alert only. Going straight to deny mode without tuning is how you get a 2 AM call about batch jobs failing on a false positive.
intrusion_detection {
mode = "Deny"
signature_overrides {
id = "2024897"
state = "Alert"
}
traffic_bypass {
name = "bypass-backup-traffic"
protocol = "TCP"
destination_ports = ["10000"]
source_addresses = ["10.30.8.0/24"]
destination_addresses = ["10.10.2.0/24"]
}
}
Operations
Send resource logs to Log Analytics using the resource specific tables, not the legacy AzureDiagnostics table, the queries are dramatically faster. Alert on SNAT port utilization above 80 percent and add public IPs before you run out. Review the rule base quarterly and delete what no longer matches traffic, because firewall rules only ever accumulate unless someone owns pruning them.
Cheers
Osama
Leave a comment