This check flags Azure VM network interfaces that have no Network Security Group attached, which means traffic to that NIC is governed only by subnet-level rules (or nothing at all). Attach an NSG to the NIC or subnet with explicit deny-by-default rules to restrict inbound and outbound traffic.
A Network Security Group (NSG) is Azure's basic stateful firewall. It controls which traffic is allowed to reach and leave a resource based on rules for source, destination, port, and protocol. When a VM's network interface has no NSG attached, the only thing standing between that VM and the network is whatever is applied at the subnet level. If the subnet has no NSG either, the VM is wide open within its virtual network and, depending on its public IP and routing, potentially open to the internet.
This Lensix check, vm_nonsg, looks at every Azure VM network interface and reports the ones with no NSG association. It is one of the most common findings in real Azure environments because Azure does not force you to attach an NSG when you create a NIC or a VM.
What this check detects
Azure lets you attach an NSG at two levels:
- Subnet level — the NSG applies to all resources in the subnet.
- Network interface (NIC) level — the NSG applies only to that specific NIC.
This check specifically reports network interfaces that have no NSG directly attached to the NIC. It does not necessarily mean the VM has zero protection, because a subnet-level NSG may still apply. But a NIC with no NSG is a signal worth investigating, because it removes a layer of defense and relies entirely on the subnet configuration being correct.
Note: When both a subnet NSG and a NIC NSG exist, Azure evaluates both. For inbound traffic, the subnet NSG is processed first, then the NIC NSG. For outbound, the NIC NSG is processed first, then the subnet. Traffic must be allowed by both to pass. This is why defense in depth at the NIC level matters even when the subnet is locked down.
Why it matters
A NIC without an NSG is a missing control, and missing controls are where incidents come from. Here are the concrete risks.
Reliance on subnet rules that may not exist or may be too broad
Many teams put dozens of VMs into a single subnet. If that subnet's NSG allows broad ranges (for example, RDP or SSH from Internet or from large internal CIDR blocks), every VM in it inherits that exposure. A NIC-level NSG lets you tighten rules per workload, for example allowing management access only to a jump host while blocking it for application servers.
Public-facing VMs with no firewall
If a VM has a public IP and its NIC and subnet both lack an NSG, the VM is directly reachable from the internet on any port the OS is listening on. Attackers continuously scan Azure IP ranges for exposed RDP (3389) and SSH (22). An unprotected Windows VM with RDP open is a textbook entry point for ransomware.
Danger: A VM with a public IP, no NSG, and RDP or SSH listening is effectively advertising a brute-force target to the entire internet. These are routinely compromised within hours of being exposed. Treat any public-facing NIC without an NSG as a high-priority finding.
Lateral movement inside the VNet
Even without a public IP, an unprotected NIC trusts the whole VNet by default. If one machine in your environment is compromised, flat internal networking lets the attacker pivot to the unprotected VM with no firewall in the way. NSGs let you segment east-west traffic and slow lateral movement.
Compliance gaps
Frameworks like CIS Azure Foundations, PCI DSS, and SOC 2 expect network-level access controls on compute resources. A NIC with no NSG is a finding auditors will flag, and it can hold up an attestation.
How to fix it
You have two main options: attach an NSG to the NIC, or confirm and rely on a properly configured subnet NSG. For most workloads, attaching an NSG at the subnet level is the cleaner approach because it scales better, but NIC-level NSGs are useful for per-VM exceptions.
Step 1: Find the affected NIC and its current state
# List all NICs and whether they have an NSG attached
az network nic list \
--query "[].{Name:name, RG:resourceGroup, NSG:networkSecurityGroup.id}" \
--output table
Any row with an empty NSG column is a NIC this check would flag.
Step 2: Create an NSG (if you do not already have a suitable one)
az network nsg create \
--resource-group myResourceGroup \
--name myVM-nsg \
--location eastus
Step 3: Add deny-by-default and explicit allow rules
Start restrictive. Azure includes default rules that allow VNet-internal traffic and deny inbound internet, but you should add explicit rules for exactly what you need. For example, allow SSH only from a specific management IP:
az network nsg rule create \
--resource-group myResourceGroup \
--nsg-name myVM-nsg \
--name Allow-SSH-From-Bastion \
--priority 100 \
--direction Inbound \
--access Allow \
--protocol Tcp \
--source-address-prefixes 203.0.113.10/32 \
--destination-port-ranges 22 \
--destination-address-prefixes '*'
Warning: Before you attach an NSG, check what ports your application and operators actually use. Attaching a restrictive NSG without an allow rule for, say, RDP or your app's port can lock out management access or break the service. Confirm the required ports first, then attach.
Step 4: Attach the NSG to the NIC
az network nic update \
--resource-group myResourceGroup \
--name myVM-nic \
--network-security-group myVM-nsg
Or attach to the subnet (recommended for shared workloads)
az network vnet subnet update \
--resource-group myResourceGroup \
--vnet-name myVNet \
--name myAppSubnet \
--network-security-group myVM-nsg
Console steps
- Open the Azure Portal and go to the VM, then select Networking.
- Find the network interface listed under the networking blade.
- Click the NIC, choose Network security group under Settings.
- Select Edit, pick an existing NSG or create a new one, and save.
Tip: For management access like SSH and RDP, skip public exposure entirely and use Azure Bastion or a Just-in-Time VM access policy in Microsoft Defender for Cloud. JIT keeps the management ports closed and opens them only on request, for a limited window, from a specific source IP.
How to prevent it from happening again
Fixing one NIC is easy. Stopping the pattern across an entire subscription is the real win. Use policy and IaC so unprotected NICs never reach production.
Enforce with Azure Policy
Azure has a built-in policy that audits network interfaces without an NSG. You can assign it in audit mode first, then move to deny once you have remediated existing resources.
# Assign the built-in policy that flags NICs without an NSG
az policy assignment create \
--name require-nsg-on-nic \
--display-name "Network interfaces should have an NSG associated" \
--policy "83a86a26-fd1f-447c-b59d-e51f44264114" \
--scope "/subscriptions/"
Note: Policy IDs can change as Microsoft updates built-in definitions. Look up the current definition with az policy definition list --query "[?contains(displayName, 'Network interfaces should')]" before assigning, so you reference the right one for your tenant.
Bake the NSG into your IaC
Never create a VM without an NSG in your templates. Here is a Terraform example that creates the NSG and associates it with the NIC in the same module, so a NIC literally cannot ship without one.
resource "azurerm_network_security_group" "vm" {
name = "myVM-nsg"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
security_rule {
name = "Allow-SSH-From-Bastion"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "203.0.113.10/32"
destination_address_prefix = "*"
}
}
resource "azurerm_network_interface_security_group_association" "vm" {
network_interface_id = azurerm_network_interface.vm.id
network_security_group_id = azurerm_network_security_group.vm.id
}
Add a CI/CD gate
Run a policy-as-code scanner on your Terraform or Bicep in the pipeline so the build fails before anything is applied. Tools like Checkov or tfsec catch missing NSG associations.
# Fail the pipeline on Azure network misconfigurations
checkov -d ./infra --framework terraform \
--check CKV_AZURE_119,CKV2_AZURE_31
Tip: Pair the pipeline scan with continuous monitoring in Lensix so that drift, like someone detaching an NSG by hand in the portal, is caught even when it bypasses your CI/CD process.
Best practices
- Default to subnet-level NSGs. Apply an NSG to every subnet so new resources inherit a baseline. Use NIC-level NSGs only for per-VM exceptions, and document why each one exists.
- Deny by default. Build rules from a closed posture and open only the specific ports and sources you need. Avoid
*in source address prefixes for management ports. - Never expose RDP or SSH to the internet. Use Azure Bastion, a VPN, or Just-in-Time access instead of a permanent inbound rule from
Internet. - Use service tags and ASGs. Application Security Groups let you write rules against logical groups of VMs instead of hardcoding IPs, which keeps rules readable as your environment grows.
- Turn on NSG flow logs. Send them to a Log Analytics workspace so you have visibility into what traffic your rules are actually allowing and denying.
- Review rules regularly. Stale allow rules accumulate. Audit your NSGs on a schedule and remove anything that no longer maps to a live requirement.
An NSG is not the whole answer to network security, but a NIC without one is a hole you can close in minutes. Treat every unprotected interface as a default to fix, not a decision to defend.
Once you have remediated existing findings and added policy plus IaC enforcement, this check should stay green. If it flips back to failing, that is a strong signal someone made a manual change that bypassed your controls, and it is worth investigating how.

