This check flags Azure Virtual Networks that contain only one subnet, which usually means workloads share a flat network with no tier separation. Split your VNet into purpose-built subnets (web, app, data, management) and apply Network Security Groups to each so you can enforce segmentation between tiers.
A single subnet inside a VNet is rarely a deliberate design decision. More often it is what happens when someone spins up a network quickly, drops every resource into the default subnet, and never circles back. It works, traffic flows, and nobody notices until the day an attacker lands on one VM and finds nothing standing between them and your database.
This Lensix check, network_singlesubnet in the network_checks module, looks at every Azure Virtual Network in scope and raises a finding when it contains exactly one subnet. It is a structural signal that your network probably has no internal segmentation.
What this check detects
The check enumerates the subnets within each Azure VNet and flags any VNet where the subnet count is one. That is the whole test. It does not inspect routing or NSG rules directly, it just identifies networks that are architecturally flat.
Why focus on subnet count? Because a subnet is the boundary at which most Azure network controls operate. Network Security Groups, route tables, service endpoints, and delegations are all attached at the subnet level. A VNet with one subnet is a VNet where every resource sits in the same security and routing context. There is no place to put a control between two workloads because they live in the same blast radius.
Note: When you create a VNet in the Azure portal, it ships with a single subnet named default using the full or near-full address space. If you never add more subnets, that default becomes the home for everything you deploy.
Why it matters
Network segmentation is one of the oldest and most effective defenses in security, and a flat VNet throws it away. Here is what that costs you in practice.
Lateral movement is trivial
Imagine a public-facing web VM gets compromised through an unpatched application. If that VM shares a subnet with your application servers and database, the attacker can reach all of them directly. There is no NSG between the web tier and the data tier to stop the next hop, because they are the same tier. Segmentation would force the attacker to break through an additional control to move deeper. A flat network hands them the whole environment.
You cannot apply least privilege at the network layer
Different workloads have different exposure needs. A web frontend should accept traffic on 443 from the internet. A database should accept traffic on 1433 only from the app tier. When everything lives in one subnet, any NSG rule you write applies to all of it, so you are forced to choose between rules that are too permissive or rules that break legitimate traffic.
Warning: A single overly broad NSG rule on a flat subnet is a common way databases end up reachable from the internet. If the subnet allows inbound 443 for the web app and the database happens to share it, a misconfigured rule can expose the database too.
Compliance frameworks expect segmentation
PCI DSS, HIPAA, and most internal security baselines require separation between systems handling sensitive data and the rest of the environment. A flat VNet makes that separation impossible to demonstrate. Auditors will not accept "all resources are in one subnet but we trust our NSG rules" as a control.
Operational blast radius grows
Beyond security, a single subnet limits how you scale and operate. You cannot route management traffic separately, you cannot delegate a subnet to a service like Azure SQL Managed Instance or App Service VNet integration, and you cannot apply different route tables to push specific tiers through a firewall.
How to fix it
The fix is to introduce additional subnets that map to your application tiers and then move or deploy resources into the appropriate subnet. The exact tier model depends on your workload, but a common pattern is web, app, data, and management.
Step 1: Inspect the current VNet
az network vnet show \
--resource-group myResourceGroup \
--name myVnet \
--query "{addressSpace:addressSpace.addressPrefixes, subnets:subnets[].{name:name, prefix:addressPrefix}}" \
-o jsonc
This shows you the VNet address space and the current subnets so you can plan how to carve out new ranges without overlapping.
Step 2: Add purpose-built subnets
Pick non-overlapping CIDR ranges inside your VNet address space. For a VNet using 10.0.0.0/16, you might define:
# Web tier
az network vnet subnet create \
--resource-group myResourceGroup \
--vnet-name myVnet \
--name snet-web \
--address-prefixes 10.0.1.0/24
# App tier
az network vnet subnet create \
--resource-group myResourceGroup \
--vnet-name myVnet \
--name snet-app \
--address-prefixes 10.0.2.0/24
# Data tier
az network vnet subnet create \
--resource-group myResourceGroup \
--vnet-name myVnet \
--name snet-data \
--address-prefixes 10.0.3.0/24
Step 3: Attach an NSG to each subnet
Subnets without NSGs give you separation but not enforcement. Create an NSG per tier and associate it so you can write rules that reflect each tier's real needs.
az network nsg create \
--resource-group myResourceGroup \
--name nsg-data
# Allow only the app subnet to reach the database port
az network nsg rule create \
--resource-group myResourceGroup \
--nsg-name nsg-data \
--name Allow-App-To-SQL \
--priority 100 \
--direction Inbound \
--access Allow \
--protocol Tcp \
--source-address-prefixes 10.0.2.0/24 \
--destination-port-ranges 1433
az network vnet subnet update \
--resource-group myResourceGroup \
--vnet-name myVnet \
--name snet-data \
--network-security-group nsg-data
Step 4: Move resources into the correct subnet
Danger: Moving a NIC to a different subnet briefly disconnects the VM and changes its private IP if you are not using a static assignment. Do this in a maintenance window and confirm dependent services use DNS names rather than hardcoded IPs.
For an existing VM, update the network interface to point at the new subnet:
az network nic ip-config update \
--resource-group myResourceGroup \
--nic-name myVmNic \
--name ipconfig1 \
--subnet snet-data \
--vnet-name myVnet
Tip: Rather than migrating live workloads NIC by NIC, it is often cleaner to redeploy into a freshly designed VNet through your IaC pipeline and cut over with DNS. You avoid IP churn and end up with a network whose structure lives in code.
Define it in Terraform instead
Encoding the segmented design in infrastructure as code makes it repeatable and reviewable.
resource "azurerm_virtual_network" "main" {
name = "vnet-prod"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
address_space = ["10.0.0.0/16"]
}
locals {
subnets = {
web = "10.0.1.0/24"
app = "10.0.2.0/24"
data = "10.0.3.0/24"
mgmt = "10.0.4.0/24"
}
}
resource "azurerm_subnet" "tiers" {
for_each = local.subnets
name = "snet-${each.key}"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = [each.value]
}
How to prevent it from happening again
Fixing one VNet is good, but the goal is to stop flat networks from being created in the first place. Push the control to the left.
Block single-subnet VNets with Azure Policy
Azure Policy can audit or deny VNets that do not meet a minimum subnet count. A custom policy that inspects Microsoft.Network/virtualNetworks/subnets[*] can flag networks with too few subnets at deployment time. Start in audit mode to measure your existing estate, then move to deny once teams have migrated.
Note: Azure Policy evaluates array fields with count expressions. You can express "the number of subnets is less than 2" using a count condition against the subnets array, then set the effect to deny for new or updated VNets.
Gate it in CI/CD
If your networks are defined in Terraform or Bicep, you do not need to wait for deployment to catch a flat VNet. Run a policy-as-code check in the pipeline:
# Example: fail the build if any VNet plan defines fewer than two subnets
terraform plan -out tfplan
terraform show -json tfplan | \
conftest test - --policy ./policies/network
An Open Policy Agent rule can assert that every azurerm_virtual_network in the plan has at least two associated azurerm_subnet resources, failing the merge before anything reaches Azure.
Run Lensix continuously
Policy and CI gates catch new resources. Continuous scanning with Lensix catches drift and the networks that predate your guardrails, so a VNet someone created manually in the portal last quarter still surfaces as a finding.
Best practices
- Design tiers before you build. Decide on your web, app, data, and management subnets up front, with reserved address ranges, so growth does not force ugly retrofits.
- Always attach an NSG to every subnet. Segmentation without enforcement is just labeling. The NSG is what actually stops traffic between tiers.
- Keep management traffic separate. A dedicated management subnet, ideally reachable only through a bastion or jump host, keeps admin paths out of the data plane.
- Leave room for service subnets. Many Azure services such as Azure SQL Managed Instance, App Service integration, and Azure Bastion require their own delegated or named subnet. Plan address space for them.
- Do not oversize subnets. A handful of large subnets recreates the flat problem at a smaller scale. Right-size each range to its tier.
- Document the model. Keep the subnet layout and intended traffic flows in your repo alongside the IaC so reviewers understand what each tier is allowed to do.
A single-subnet VNet is easy to create and easy to forget, but it removes the one place where Azure lets you draw lines between your workloads. Carve the network into tiers, enforce those tiers with NSGs, and codify the layout so the flat design never comes back.

