
Azure NAT Gateway with Terraform: Designing Outbound Access for Private Subnets
- Posted by Martin Linxfeld
- Categories Azure Design Notes
- Date February 13, 2026
- Comments 0 comment
- Tags Azure, Azure NAT Gateway, Azure Networking, cloud architecture, FoggyKitchen, Infrastructure as Code, network security, OpenTofu, private subnet, terraform
Private workloads still need the internet.
Not for exposure — but for controlled, auditable outbound traffic.
In Azure, outbound connectivity is often treated as an afterthought — yet Azure NAT Gateway Terraform provides a clean, architecture-grade way to design outbound identity for private subnets.
In production platforms, the real architectural question is not “does this VM have internet access?”
The real question is:
Where does the outbound identity of my private workloads live?
This article shows how Azure NAT Gateway acts as an egress boundary, not just a connectivity helper — and how to model this boundary cleanly with Terraform / OpenTofu.
❌ The common anti-pattern: public IP on a private VM
A surprisingly common solution to outbound connectivity looks like this:
VM in a “private” subnet
Public IP attached directly to the NIC
Outbound traffic works
Cloud-init can install packages
apt updatesucceeds
Architecturally, this breaks multiple design principles:
The workload now owns a public network identity
Egress identity is coupled to the VM lifecycle
Security boundaries are blurred
Auditing outbound traffic becomes harder
“Private subnet” becomes a naming convention, not a boundary
This is not an access plane.
This is accidental exposure.
✅ The architectural pattern: NAT Gateway as an egress boundary
The correct design separates concerns:
Workloads remain private
Subnets define security and traffic boundaries
Outbound traffic exits the VNet through a dedicated egress plane
This is exactly what Azure NAT Gateway provides:
A stable outbound public IP
No inbound exposure
Subnet-level attachment
Clear ownership of egress identity
Azure NAT Gateway is not about “giving internet to VMs”.
It is about centralizing outbound identity for a network tier.
👉 See the full Azure infrastructure with Terraform architecture model: Azure Infrastructure with Terraform – Architecture Model
🌐 Network foundation
Outbound traffic control starts at the network layer.
This example assumes a purpose-driven virtual network layout where subnets define traffic responsibilities — similar to the approach described here:
👉 Azure VNet Terraform Module – Explained
Once the network contract exists, egress policies such as NAT Gateway can be applied without redesigning the platform later.
🧭 Azure NAT Gateway with Terraform: outbound access for private subnets
In this reference architecture:
Backend VMs live in a private subnet
No public IPs are attached to any VM
A NAT Gateway is associated with the private subnet
Outbound traffic flows through the NAT Gateway public IP
Inbound traffic (if any) is handled separately (for example via Azure Load Balancer)
Key properties of this pattern:
Private subnet has controlled egress
Outbound IP is stable and auditable
Workloads have no public network identity
Egress plane is explicitly modeled in Infrastructure as Code
NAT Gateway defines the outbound contract for this subnet. Workloads do not “have internet access” — the platform exposes a controlled egress plane. This makes outbound traffic explicit, auditable, and decoupled from individual VMs.
🧩 Modeling the egress plane with Terraform
The following example shows how to model Azure NAT Gateway Terraform as a dedicated egress boundary for a private subnet:
module "natgw" {
source = "github.com/foggykitchen/terraform-az-fk-natgw"
name = "fk-natgw"
location = azurerm_resource_group.foggykitchen_rg.location
resource_group_name = azurerm_resource_group.foggykitchen_rg.name
create_public_ip = true
subnet_associations = {
private_subnet = {
subnet_id = module.vnet.subnet_ids["fk-subnet-private"]
}
}
tags = var.tags
}
This makes the design intent explicit:
Outbound identity is owned by the platform layer
The subnet is the egress boundary
Workloads consume the egress plane instead of owning public IPs
No hidden defaults.
No portal-driven magic.
Just architecture as code.
🔍 Verifying outbound identity from a private VM
To prove that outbound traffic flows through the NAT Gateway, verify the egress IP from the private VM.
Using Azure Portal → Run Command, execute:
curl https://ifconfig.me
Verification result:

Figure 2. Azure NAT Gateway outbound IP verification using Terraform and Run Command
The returned IP matches the public IP assigned to the NAT Gateway.
This confirms:
Outbound traffic is centralized
The VM has no independent public identity
The NAT Gateway is the single egress boundary
🎯 Why this pattern matters in real platforms
This design becomes critical when you care about:
Egress allow-listing in partner systems
Firewall rules on external services
Auditing outbound traffic
Regulatory and compliance requirements
Zero-trust networking models
Private Endpoints combined with controlled internet egress
Without NAT Gateway, outbound identity is:
Implicit
Ephemeral
Or incorrectly attached to workloads
With NAT Gateway, outbound identity becomes a platform contract.
🧠 Design notes
Some principles that emerge from this pattern:
Outbound connectivity is an architectural plane, not a VM feature
Private subnets without egress design are operationally broken
Egress identity should be owned by the platform, not the workload
NAT Gateway is a boundary, not a convenience
Terraform modules should express design intent, not just resources
This is the same design philosophy behind:
Bastion as an operator access plane
Each plane has a role. Each boundary must be explicit.
📂 Reference implementation
This article is based on the FoggyKitchen reference module:
terraform-az-fk-natgw
Reusable Terraform / OpenTofu module for Azure NAT Gateway with clean subnet-level egress patterns.
The repository includes runnable examples:
01_private_subnet_with_nat_gateway
Private VM with outbound access via NAT Gateway02_private_backend_with_lb_and_nat_gateway
Private backend tier behind a public Load Balancer with controlled outbound identity
🧭 Closing thought
If you don’t explicitly design your egress plane, your platform will design one for you — usually in the worst possible way.
Azure NAT Gateway with Terraform is not about internet access. It is about where your platform’s outbound identity lives.
🚀 If you want to see how networking, compute, storage, and private connectivity come together into a complete Azure platform — built step by step with Terraform/OpenTofu:

From NAT Gateway to Real Azure Egress Architecture
This example shows how outbound connectivity is controlled at the subnet level — but real Azure platforms require consistent egress design across all workloads.
NAT Gateway is a core building block of production-grade Azure architectures.
🔒 Lifetime • ⏱️ Self-paced • 🧪 Real labs

