Complete Configuration Guide with Visual Diagrams & CLI Commands
Inbound NAT rules in Azure allow you to forward traffic from a specific port on the load balancer's frontend IP address to a specific port on a backend virtual machine. This enables direct access to individual VMs behind a load balancer.
This diagram illustrates the fundamental concept of inbound NAT rules. Multiple internet users can access different backend VMs through a single public IP address by using different port numbers:
This diagram shows the hierarchical structure and relationships of all Azure components involved in NAT rule configuration:
This diagram shows the logical relationships between Azure components and how they depend on each other:
# Windows (using winget) winget install Microsoft.AzureCLI # macOS (using Homebrew) brew install azure-cli # Linux (Ubuntu/Debian) curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
az login # Set your subscription (if you have multiple) az account set --subscription "Your-Subscription-Name-or-ID" # Verify your current subscription az account show --output table
# Resource naming variables RESOURCE_GROUP="rg-nat-demo" LOCATION="eastus" VNET_NAME="vnet-nat-demo" SUBNET_NAME="subnet-backend" LB_NAME="lb-nat-demo" PUBLIC_IP_NAME="pip-nat-demo" NSG_NAME="nsg-nat-demo" # Network configuration VNET_PREFIX="10.0.0.0/16" SUBNET_PREFIX="10.0.1.0/24" # VM configuration VM_SIZE="Standard_B2s" ADMIN_USERNAME="azureuser" VM_IMAGE="Ubuntu2204"
az group create \
--name $RESOURCE_GROUP \
--location $LOCATION \
--tags Environment=Demo Purpose=NAT-Rules
az network vnet create \
--resource-group $RESOURCE_GROUP \
--name $VNET_NAME \
--address-prefix $VNET_PREFIX \
--subnet-name $SUBNET_NAME \
--subnet-prefix $SUBNET_PREFIX \
--location $LOCATION
# Create NSG
az network nsg create \
--name $NSG_NAME \
--resource-group $RESOURCE_GROUP \
--location $LOCATION
# Allow SSH access through NAT rules
az network nsg rule create \
--resource-group $RESOURCE_GROUP \
--nsg-name $NSG_NAME \
--name Allow-SSH-NAT \
--priority 1000 \
--protocol Tcp \
--destination-port-ranges 22 \
--access Allow \
--direction Inbound
# Allow RDP access through NAT rules
az network nsg rule create \
--resource-group $RESOURCE_GROUP \
--nsg-name $NSG_NAME \
--name Allow-RDP-NAT \
--priority 1010 \
--protocol Tcp \
--destination-port-ranges 3389 \
--access Allow \
--direction Inbound
# Allow custom application ports
az network nsg rule create \
--resource-group $RESOURCE_GROUP \
--nsg-name $NSG_NAME \
--name Allow-HTTP \
--priority 1020 \
--protocol Tcp \
--destination-port-ranges 80 \
--access Allow \
--direction Inbound
az network public-ip create \
--name $PUBLIC_IP_NAME \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--allocation-method Static \
--sku Standard \
--tier Regional
az network lb create \
--name $LB_NAME \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--frontend-ip-name LoadBalancerFrontend \
--public-ip-address $PUBLIC_IP_NAME \
--backend-pool-name BackendPool \
--sku Standard
# Create VM1
az vm create \
--name vm-web-01 \
--resource-group $RESOURCE_GROUP \
--image $VM_IMAGE \
--size $VM_SIZE \
--admin-username $ADMIN_USERNAME \
--generate-ssh-keys \
--vnet-name $VNET_NAME \
--subnet $SUBNET_NAME \
--nsg $NSG_NAME \
--public-ip-address "" \
--no-wait
# Create VM2
az vm create \
--name vm-web-02 \
--resource-group $RESOURCE_GROUP \
--image $VM_IMAGE \
--size $VM_SIZE \
--admin-username $ADMIN_USERNAME \
--generate-ssh-keys \
--vnet-name $VNET_NAME \
--subnet $SUBNET_NAME \
--nsg $NSG_NAME \
--public-ip-address "" \
--no-wait
# Create VM3
az vm create \
--name vm-web-03 \
--resource-group $RESOURCE_GROUP \
--image $VM_IMAGE \
--size $VM_SIZE \
--admin-username $ADMIN_USERNAME \
--generate-ssh-keys \
--vnet-name $VNET_NAME \
--subnet $SUBNET_NAME \
--nsg $NSG_NAME \
--public-ip-address "" \
--no-wait
# Wait for all VMs to be created
az vm wait --resource-group $RESOURCE_GROUP --name vm-web-01 --created
az vm wait --resource-group $RESOURCE_GROUP --name vm-web-02 --created
az vm wait --resource-group $RESOURCE_GROUP --name vm-web-03 --created
# Get the network interface IDs
VM1_NIC_ID=$(az vm show --name vm-web-01 --resource-group $RESOURCE_GROUP --query 'networkProfile.networkInterfaces[0].id' -o tsv)
VM2_NIC_ID=$(az vm show --name vm-web-02 --resource-group $RESOURCE_GROUP --query 'networkProfile.networkInterfaces[0].id' -o tsv)
VM3_NIC_ID=$(az vm show --name vm-web-03 --resource-group $RESOURCE_GROUP --query 'networkProfile.networkInterfaces[0].id' -o tsv)
# Add NICs to backend pool
az network nic ip-config address-pool add \
--address-pool BackendPool \
--ip-config-name ipconfig1 \
--nic-name vm-web-01VMNic \
--resource-group $RESOURCE_GROUP \
--lb-name $LB_NAME
az network nic ip-config address-pool add \
--address-pool BackendPool \
--ip-config-name ipconfig1 \
--nic-name vm-web-02VMNic \
--resource-group $RESOURCE_GROUP \
--lb-name $LB_NAME
az network nic ip-config address-pool add \
--address-pool BackendPool \
--ip-config-name ipconfig1 \
--nic-name vm-web-03VMNic \
--resource-group $RESOURCE_GROUP \
--lb-name $LB_NAME
# NAT Rule for VM1 SSH (Port 2201 -> 22)
az network lb inbound-nat-rule create \
--name SSH-VM1 \
--resource-group $RESOURCE_GROUP \
--lb-name $LB_NAME \
--protocol Tcp \
--frontend-port 2201 \
--backend-port 22 \
--frontend-ip-name LoadBalancerFrontend
# NAT Rule for VM2 SSH (Port 2202 -> 22)
az network lb inbound-nat-rule create \
--name SSH-VM2 \
--resource-group $RESOURCE_GROUP \
--lb-name $LB_NAME \
--protocol Tcp \
--frontend-port 2202 \
--backend-port 22 \
--frontend-ip-name LoadBalancerFrontend
# NAT Rule for VM3 SSH (Port 2203 -> 22)
az network lb inbound-nat-rule create \
--name SSH-VM3 \
--resource-group $RESOURCE_GROUP \
--lb-name $LB_NAME \
--protocol Tcp \
--frontend-port 2203 \
--backend-port 22 \
--frontend-ip-name LoadBalancerFrontend
# NAT Rule for VM1 HTTP (Port 8001 -> 80)
az network lb inbound-nat-rule create \
--name HTTP-VM1 \
--resource-group $RESOURCE_GROUP \
--lb-name $LB_NAME \
--protocol Tcp \
--frontend-port 8001 \
--backend-port 80 \
--frontend-ip-name LoadBalancerFrontend
# NAT Rule for VM2 HTTP (Port 8002 -> 80)
az network lb inbound-nat-rule create \
--name HTTP-VM2 \
--resource-group $RESOURCE_GROUP \
--lb-name $LB_NAME \
--protocol Tcp \
--frontend-port 8002 \
--backend-port 80 \
--frontend-ip-name LoadBalancerFrontend
# NAT Rule for VM3 HTTP (Port 8003 -> 80)
az network lb inbound-nat-rule create \
--name HTTP-VM3 \
--resource-group $RESOURCE_GROUP \
--lb-name $LB_NAME \
--protocol Tcp \
--frontend-port 8003 \
--backend-port 80 \
--frontend-ip-name LoadBalancerFrontend
# Associate SSH NAT rules
az network nic ip-config inbound-nat-rule add \
--resource-group $RESOURCE_GROUP \
--nic-name vm-web-01VMNic \
--ip-config-name ipconfig1 \
--inbound-nat-rule SSH-VM1 \
--lb-name $LB_NAME
az network nic ip-config inbound-nat-rule add \
--resource-group $RESOURCE_GROUP \
--nic-name vm-web-02VMNic \
--ip-config-name ipconfig1 \
--inbound-nat-rule SSH-VM2 \
--lb-name $LB_NAME
az network nic ip-config inbound-nat-rule add \
--resource-group $RESOURCE_GROUP \
--nic-name vm-web-03VMNic \
--ip-config-name ipconfig1 \
--inbound-nat-rule SSH-VM3 \
--lb-name $LB_NAME
# Associate HTTP NAT rules
az network nic ip-config inbound-nat-rule add \
--resource-group $RESOURCE_GROUP \
--nic-name vm-web-01VMNic \
--ip-config-name ipconfig1 \
--inbound-nat-rule HTTP-VM1 \
--lb-name $LB_NAME
az network nic ip-config inbound-nat-rule add \
--resource-group $RESOURCE_GROUP \
--nic-name vm-web-02VMNic \
--ip-config-name ipconfig1 \
--inbound-nat-rule HTTP-VM2 \
--lb-name $LB_NAME
az network nic ip-config inbound-nat-rule add \
--resource-group $RESOURCE_GROUP \
--nic-name vm-web-03VMNic \
--ip-config-name ipconfig1 \
--inbound-nat-rule HTTP-VM3 \
--lb-name $LB_NAME
This diagram shows the exact sequence of events when a client connects through an inbound NAT rule:
This diagram illustrates the complete journey of a network packet from the internet to a specific VM through Azure's infrastructure:
# Get the public IP address for connection testing
PUBLIC_IP=$(az network public-ip show \
--name $PUBLIC_IP_NAME \
--resource-group $RESOURCE_GROUP \
--query ipAddress \
--output tsv)
echo "Load Balancer Public IP: $PUBLIC_IP"
# List all inbound NAT rules
az network lb inbound-nat-rule list \
--lb-name $LB_NAME \
--resource-group $RESOURCE_GROUP \
--output table
# Show complete load balancer configuration
az network lb show \
--name $LB_NAME \
--resource-group $RESOURCE_GROUP \
--output json | jq '{
name: .name,
frontendIPConfigurations: .frontendIPConfigurations,
backendAddressPools: .backendAddressPools,
inboundNatRules: .inboundNatRules
}'
# Test SSH to VM1 through NAT rule ssh -p 2201 $ADMIN_USERNAME@$PUBLIC_IP # Test SSH to VM2 through NAT rule ssh -p 2202 $ADMIN_USERNAME@$PUBLIC_IP # Test SSH to VM3 through NAT rule ssh -p 2203 $ADMIN_USERNAME@$PUBLIC_IP
# Connect to VM1 and install nginx ssh -p 2201 $ADMIN_USERNAME@$PUBLIC_IP << 'EOF' sudo apt update sudo apt install -y nginx echo "Hello from VM1
" | sudo tee /var/www/html/index.html sudo systemctl start nginx sudo systemctl enable nginx exit EOF # Test HTTP access through NAT rule curl http://$PUBLIC_IP:8001
# Create NAT rule for custom application (e.g., Node.js on port 3000)
az network lb inbound-nat-rule create \
--name NodeJS-VM1 \
--resource-group $RESOURCE_GROUP \
--lb-name $LB_NAME \
--protocol Tcp \
--frontend-port 9001 \
--backend-port 3000 \
--frontend-ip-name LoadBalancerFrontend
# Associate with VM1
az network nic ip-config inbound-nat-rule add \
--resource-group $RESOURCE_GROUP \
--nic-name vm-web-01VMNic \
--ip-config-name ipconfig1 \
--inbound-nat-rule NodeJS-VM1 \
--lb-name $LB_NAME
# Create NAT rule for database access (e.g., PostgreSQL)
az network lb inbound-nat-rule create \
--name PostgreSQL-VM2 \
--resource-group $RESOURCE_GROUP \
--lb-name $LB_NAME \
--protocol Tcp \
--frontend-port 5432 \
--backend-port 5432 \
--frontend-ip-name LoadBalancerFrontend
# Associate with VM2
az network nic ip-config inbound-nat-rule add \
--resource-group $RESOURCE_GROUP \
--nic-name vm-web-02VMNic \
--ip-config-name ipconfig1 \
--inbound-nat-rule PostgreSQL-VM2 \
--lb-name $LB_NAME
# Check connection status
az network lb show \
--name $LB_NAME \
--resource-group $RESOURCE_GROUP \
--query "inboundNatRules[].{Name:name,FrontendPort:frontendPort,BackendPort:backendPort,Protocol:protocol}" \
--output table
# Check backend pool members
az network lb address-pool show \
--name BackendPool \
--lb-name $LB_NAME \
--resource-group $RESOURCE_GROUP \
--query "backendIpConfigurations[].{Name:id}" \
--output table
# Test network connectivity
az network watcher test-connectivity \
--source-resource vm-web-01 \
--dest-address google.com \
--dest-port 80 \
--resource-group $RESOURCE_GROUP
# Check effective security rules
az network nic list-effective-nsg \
--name vm-web-01VMNic \
--resource-group $RESOURCE_GROUP
This comprehensive diagram demonstrates real-world scenarios with multiple client types accessing different services:
ssh -p 2201 admin@20.120.45.123 → VM1 SSH servicehttp://20.120.45.123:8001 → VM1 Nginx serverpsql -h 20.120.45.123 -p 5432 → VM2 PostgreSQL# Delete the entire resource group (removes all resources)
az group delete \
--name $RESOURCE_GROUP \
--yes \
--no-wait
# Or delete individual NAT rules
az network lb inbound-nat-rule delete \
--name SSH-VM1 \
--lb-name $LB_NAME \
--resource-group $RESOURCE_GROUP