Quick summary
Before going deeper, here is the simplest way to think about Azure NSG Terraform.
azurerm_network_security_group and usually azurerm_network_security_rule.
Why use Terraform for Azure NSGs?
NSGs can be created manually in the Azure portal, but Terraform is better when you need standardization, peer review, repeatable rule creation, and cleaner multi-environment rollout.
- It keeps traffic-filtering rules in code instead of clicks.
- It makes rule changes visible in Git and code review.
- It reduces manual mistakes in priorities, naming, and port/IP matching.
- It helps platform teams roll out consistent security policy across environments.
- It fits well into CI/CD and reusable Terraform modules.
Terraform code
This example creates:
- an Azure resource group
- one Azure Network Security Group
- three explicit rules inside that NSG
terraform {
required_version = ">= 1.5.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.100"
}
}
}
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "network_rg" {
name = var.resource_group_name
location = var.location
}
resource "azurerm_network_security_group" "web_nsg" {
name = var.nsg_name
location = azurerm_resource_group.network_rg.location
resource_group_name = azurerm_resource_group.network_rg.name
tags = {
environment = var.environment
managed_by = "terraform"
purpose = "traffic-filtering"
}
}
resource "azurerm_network_security_rule" "allow_https_inbound" {
name = "Allow-HTTPS-Inbound"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "Internet"
destination_address_prefix = "*"
resource_group_name = azurerm_resource_group.network_rg.name
network_security_group_name = azurerm_network_security_group.web_nsg.name
}
resource "azurerm_network_security_rule" "allow_app_to_db" {
name = "Allow-App-To-DB"
priority = 110
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "1433"
source_address_prefix = "10.20.2.0/24"
destination_address_prefix = "10.20.3.0/24"
resource_group_name = azurerm_resource_group.network_rg.name
network_security_group_name = azurerm_network_security_group.web_nsg.name
}
resource "azurerm_network_security_rule" "deny_all_inbound" {
name = "Deny-All-Inbound"
priority = 400
direction = "Inbound"
access = "Deny"
protocol = "*"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = "*"
destination_address_prefix = "*"
resource_group_name = azurerm_resource_group.network_rg.name
network_security_group_name = azurerm_network_security_group.web_nsg.name
}
Code breakdown
Terraform block
This defines the Terraform version and AzureRM provider version. Keeping provider versions controlled helps avoid surprise changes later.
Provider block
The AzureRM provider is what Terraform uses to communicate with Azure and manage resources.
Resource group
Azure resources need a resource group, so this is usually created early in the configuration.
Network security group resource
The azurerm_network_security_group resource creates the NSG container itself. The actual traffic behavior comes from the rules attached to it.
Network security rule resources
The azurerm_network_security_rule resources define how Azure evaluates traffic by source, destination, protocol, port, direction, priority, and action.
Why separate rule resources are helpful
Many teams prefer separate rule resources because they are easier to read, review, and manage than very large inline rule blocks inside the NSG resource.
Important NSG rule fields in Terraform
A good Azure NSG Terraform page should explain that rules are not only about “open port” logic. Each rule is really a traffic-matching definition.
| Field | What it controls | Example |
|---|---|---|
| direction | Whether the rule applies to inbound or outbound traffic | Inbound |
| access | Whether the rule allows or denies matching traffic | Allow |
| protocol | Which protocol the rule applies to | Tcp |
| source_address_prefix | Where the traffic starts from | Internet or 10.20.2.0/24 |
| destination_address_prefix | Where the traffic is going | 10.20.3.0/24 or * |
| source_port_range | The client-side source port | * |
| destination_port_range | The target service port | 443, 22, 1433 |
| priority | The order Azure evaluates the rule | 100 |
Suggested variables file
Variables make NSG Terraform code more reusable across development, testing, and production environments.
variable "resource_group_name" {
description = "Name of the Azure resource group"
type = string
}
variable "location" {
description = "Azure region for deployment"
type = string
default = "South Africa North"
}
variable "nsg_name" {
description = "Name of the Azure Network Security Group"
type = string
}
variable "environment" {
description = "Environment name"
type = string
default = "dev"
}
Example terraform.tfvars
resource_group_name = "rg-network-dev-san-1"
location = "South Africa North"
nsg_name = "nsg-web-dev-san-1"
environment = "dev"
Suggested outputs
Outputs help expose NSG IDs and names so they can be used by subnet association resources, NIC association resources, or downstream modules.
output "nsg_name" {
value = azurerm_network_security_group.web_nsg.name
}
output "nsg_id" {
value = azurerm_network_security_group.web_nsg.id
}
How to run it
A simple Terraform execution flow looks like this:
terraform init
terraform fmt
terraform validate
terraform plan
terraform apply
In real teams, these steps often run through CI/CD after review rather than manually every time.
terraform fmt and terraform validate before planning or applying changes.
Real-world usage
This NSG Terraform pattern can grow into real production security policy workflows.
Web-tier protection
A platform team allows only HTTPS from the internet to the web subnet, while denying all other direct inbound traffic.
Database protection
A team allows SQL traffic on destination port 1433 only from the application subnet source range, not from other sources.
Standardized security module
Terraform modules define repeatable NSG patterns so multiple application teams get consistent security baselines across environments.
Best practices
- Use clear rule names that reflect purpose.
- Be deliberate with priority numbers.
- Keep source and destination matching specific where possible.
- Focus on destination service ports carefully.
- Use separate Terraform rule resources for readability in larger environments.
- Document why a rule exists, not just what it allows.
- Avoid broad allow rules unless truly required.
- Keep NSG design aligned with subnet purpose and traffic flow.
Common mistakes
- Using the correct destination port but the wrong source range.
- Creating rules that never match because of priority order.
- Using too many broad allow rules.
- Hardcoding too much instead of using variables.
- Confusing NSG creation with NSG association.
- Ignoring whether the rule should be inbound or outbound.
Frequently asked questions
What Terraform resource creates an Azure NSG?
The azurerm_network_security_group resource creates an Azure NSG.
What Terraform resource creates an NSG rule?
The azurerm_network_security_rule resource is commonly used to create individual NSG rules.
Should I define NSG rules inline or separately?
For small examples either can work, but many teams prefer separate rule resources for better readability and control.
What should I learn after Azure NSG Terraform?
A good next step is Route Tables Terraform, subnet and NIC associations, and more advanced topics like private endpoints and hub-and-spoke security patterns.
Summary
Azure NSG Terraform is one of the best practical ways to learn how Azure traffic-filtering policy becomes infrastructure as code. It teaches you how rule logic is expressed, how ports and IPs are matched, and how reusable security foundations are built in real environments.
Official Azure documentation
These official references are useful if you want deeper platform details for Azure NSGs and the Terraform resources used to create them.