Introduction
Security in the cloud starts with properly configured network access rules. In Azure, Network Security Groups (NSG) act like a firewall that controls inbound and outbound traffic to resources within a virtual network. Think of NSGs as access lists that say: βWho can talk to what, on which ports.β
π When Do You Need NSGs?
If you have:
-
Virtual machines that need public access,
-
A database that should be isolated,
-
Or multiple subnets that must not communicate freely with each other β
then NSGs are your first line of defense.
π Step-by-Step Configuration
Step 1: Define What You Want to Protect
Before creating anything, decide:
-
Which resources need to be protected (VMs, subnets)?
-
What ports should be open?
-
Who needs access?
Example:
-
Web frontend: open 80/443 to the internet
-
Backend: accessible only from frontend subnet
-
Database: accessible only from backend subnet
Step 2: Create an NSG
-
Go to the Azure Portal.
-
Search for Network Security Groups
-
Click + Create
-
Fill in:
-
Name (e.g.,
nsg-web
) -
Region (must match the subnet’s region)
-
Resource Group
-
-
Click Review + Create, then Create
Step 3: Associate the NSG with a Subnet or NIC
-
Open your newly created NSG.
-
In the left menu, select Subnets or Network Interfaces
-
Click Associate
-
Choose your VNet and target subnet or NIC.
π‘ Best practice: associate NSGs with subnets, not individual VMs β it’s easier to manage at scale.
Step 4: Add Inbound Rules
-
Go to Inbound security rules
-
Click + Add
-
Example rule:
-
Source: Any
-
Source port: *
-
Destination: Any
-
Destination port: 80
-
Protocol: TCP
-
Action: Allow
-
Priority: 100
-
Name:
Allow-HTTP
-
-
Save the rule
π Donβt forget:
-
Add rules for SSH (22) or RDP (3389)
-
Add SQL (1433) if needed
-
Everything else is denied by default
Step 5: Add Outbound Rules (Optional)
Outbound connections are allowed by default, but you can lock them down:
-
Deny all internet access (
0.0.0.0/0
) -
Allow only specific destination IPs (e.g., Azure services)
Step 6: Verify the Rules in Action
-
Go to your VM
-
Open the Networking tab
-
Check the Effective security rules β these combine NSG, UDR, and policies
-
Test access:
-
Use tools like
curl
,telnet
,nmap
-
Or use Azure Network Watcher
-
Step 7: Enable Logging and Auditing
-
Enable NSG Flow Logs via Network Watcher
-
Send logs to:
-
A Storage Account
-
Log Analytics
-
A SIEM system
-
This gives you visibility into whoβs knocking on your ports β and when.
Step 8: Repeat for Other Subnets
Use separate NSGs per logical zone:
-
nsg-frontend
-
nsg-backend
-
nsg-database
π« Avoid using a single NSG for the entire VNet β it becomes hard to manage and troubleshoot.
π§ Best Practices
Tip | Why It Matters |
---|---|
Never allow *:* |
Massive security hole |
Use clear priorities (100β4096) | Azure processes rules top-down |
Check Effective Rules | Combines NSG + UDR + Policy behavior |
Apply to subnets, not VMs | Easier scaling and governance |
Conclusion
Proper NSG configuration isnβt just a checkbox β itβs a critical layer of defense for your cloud infrastructure. Start with minimal rules, expand only as needed, log everything, and regularly audit your setup. And don’t forget to remove rules you no longer need β in the cloud, clutter can cost you.
Azure NSG Templates
—
### Terraform Template (main.tf)
“`hcl
provider “azurerm” {
features {}
}
resource “azurerm_resource_group” “nsg_rg” {
name = “rg-nsg-demo”
location = “East US”
}
resource “azurerm_virtual_network” “vnet” {
name = “vnet-demo”
address_space = [“10.0.0.0/16”]
location = azurerm_resource_group.nsg_rg.location
resource_group_name = azurerm_resource_group.nsg_rg.name
}
resource “azurerm_subnet” “frontend” {
name = “subnet-frontend”
resource_group_name = azurerm_resource_group.nsg_rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = [“10.0.1.0/24”]
}
resource “azurerm_network_security_group” “frontend_nsg” {
name = “nsg-frontend”
location = azurerm_resource_group.nsg_rg.location
resource_group_name = azurerm_resource_group.nsg_rg.name
security_rule {
name = “AllowHTTP”
priority = 100
direction = “Inbound”
access = “Allow”
protocol = “Tcp”
source_port_range = “*”
destination_port_range = “80”
source_address_prefix = “*”
destination_address_prefix = “*”
}
}
resource “azurerm_subnet_network_security_group_association” “frontend_assoc” {
subnet_id = azurerm_subnet.frontend.id
network_security_group_id = azurerm_network_security_group.frontend_nsg.id
}
“`
—
### Bicep Template (main.bicep)
“`bicep
resource rg ‘Microsoft.Resources/resourceGroups@2021-04-01’ = {
name: ‘rg-nsg-demo’
location: ‘East US’
}
resource vnet ‘Microsoft.Network/virtualNetworks@2021-05-01’ = {
name: ‘vnet-demo’
location: rg.location
properties: {
addressSpace: {
addressPrefixes: [‘10.0.0.0/16’]
}
subnets: [
{
name: ‘subnet-frontend’
properties: {
addressPrefix: ‘10.0.1.0/24’
}
}
]
}
}
resource nsg ‘Microsoft.Network/networkSecurityGroups@2021-05-01’ = {
name: ‘nsg-frontend’
location: rg.location
properties: {
securityRules: [
{
name: ‘AllowHTTP’
properties: {
priority: 100
direction: ‘Inbound’
access: ‘Allow’
protocol: ‘Tcp’
sourcePortRange: ‘*’
destinationPortRange: ’80’
sourceAddressPrefix: ‘*’
destinationAddressPrefix: ‘*’
}
}
]
}
}
“`
—
### ARM Template (nsg-arm.json)
“`json
{
“$schema”: “https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#”,
“contentVersion”: “1.0.0.0”,
“resources”: [
{
“type”: “Microsoft.Network/networkSecurityGroups”,
“apiVersion”: “2021-05-01”,
“name”: “nsg-frontend”,
“location”: “[resourceGroup().location]”,
“properties”: {
“securityRules”: [
{
“name”: “AllowHTTP”,
“properties”: {
“priority”: 100,
“direction”: “Inbound”,
“access”: “Allow”,
“protocol”: “Tcp”,
“sourcePortRange”: “*”,
“destinationPortRange”: “80”,
“sourceAddressPrefix”: “*”,
“destinationAddressPrefix”: “*”
}
}
]
}
}
]
}
“`
—