top of page

Migration from Zonal NAT Gateways to Regional NAT Gateways

5 days ago
7 min read

NAT Gateways are a common component in AWS network architectures. They allow resources located in private subnets to initiate outbound connections to the internet without directly exposing those resources through public IP addresses.

Historically, the recommended pattern for high availability was to create one NAT Gateway per Availability Zone and configure the route tables so that each private subnet uses the NAT Gateway in its own zone. This model is still valid, but it adds operational complexity: more resources, more Elastic IPs, more routes, and more care when expanding the VPC to new Availability Zones.

AWS introduced Regional NAT Gateways, a model that allows you to create a single regional NAT Gateway associated with a VPC. In regional mode, AWS can expand and contract coverage across Availability Zones based on the presence of workloads, simplifying the design and operation of internet egress from private subnets.

This TeraTip explains what changes, what benefits it provides, what risks should be reviewed, and how to plan a safe migration.


What Changes

With zonal NAT Gateways, each NAT Gateway resides in a specific Availability Zone. For a resilient multi-AZ architecture, you would typically create one NAT Gateway per zone and maintain a 0.0.0.0/0 route in each private route table pointing to the local NAT Gateway in that zone.


With Regional NAT Gateways, you create a NAT Gateway in regional mode, and private routes can point to a single NAT Gateway ID. In automatic mode, AWS manages expansion across Availability Zones and the assignment of the public IP addresses required to provide the service.


According to the official AWS documentation, this approach is designed to simplify network architecture, improve security posture, and provide high availability by default.


Benefits of Switching to Regional NAT Gateways

The main benefits are:

  • Less network complexity: a single NAT Gateway ID can be used for private subnets across different Availability Zones, reducing zone-specific routing logic.

  • Fewer resources to operate: there is less need to create, name, tag, and maintain multiple NAT Gateways and associated routes.

  • No public subnet required to host the NAT: AWS documents the Regional NAT Gateway as an independent resource with its own route table. This reduces the risk of errors where private resources end up in subnets with public connectivity.

  • Automatic high availability: the regional NAT expands and contracts based on the presence of workloads in the Availability Zones, maintaining zonal affinity when appropriate.

  • Higher port and IP scalability: AWS indicates that Regional NAT Gateways support up to 32 IP addresses per Availability Zone, compared to 8 for zonal NAT Gateways. Each IP increases the limit by 55,000 simultaneous connections to the same destination.

  • Better abstraction for Terraform modules: it allows you to expose a simple option such as nat_gateway_availability_mode = "regional" without requiring the module consumer to manually model each Availability Zone.

  • Less risk when expanding to new AZs: instead of adding a NAT Gateway, public subnet, and routes every time a new zone is introduced, regional mode can handle that expansion automatically.


The main benefit should not be presented solely as cost savings. In many cases, the clearest benefits are operational simplicity, a lower probability of misconfiguration, and an easier-to-standardize egress architecture.


When to Use a Regional NAT Gateway

It makes sense to consider it when:

  • Internet egress is required from private subnets.

  • The VPC operates across multiple Availability Zones.

  • You want to simplify private route design.

  • You are creating a new VPC and want to avoid unnecessary complexity from the start.

  • You maintain reusable Terraform modules and want a simpler option for module consumers.

  • You are not using a Private NAT Gateway.

  • You can validate the change within a controlled maintenance window.


When to Keep Zonal NAT Gateways

Not every environment should be migrated automatically.


It makes sense to keep zonal NAT Gateways when:

  • Private NAT Gateway is required. AWS indicates that Regional NAT Gateway does not support private NAT.

  • Strict manual control per Availability Zone is required.

  • There are strong dependencies on current public egress IPs.

  • External allowlists cannot be changed easily.

  • The zone or architecture type does not support Regional NAT.

  • A maintenance window cannot be accepted.


Cost Considerations

A Regional NAT Gateway does not necessarily mean "one NAT instead of three" from a billing perspective.


The official Amazon VPC pricing page indicates that a Regional NAT Gateway is charged for each hour it is configured in each Availability Zone. For example, if a regional NAT operates across three Availability Zones for one hour, three NAT Gateway-hours are billed. GB processed by the NAT Gateway and standard data transfer charges are also applicable.


Therefore, before migrating, it is worth reviewing:

  • Outbound internet traffic volume.

  • Traffic to AWS services that could use VPC Endpoints.

  • Use of Gateway Endpoints for S3 and DynamoDB.

  • Use of Interface Endpoints for high-traffic AWS services.

  • Potential changes in processing and data transfer charges.


The change can simplify the architecture, but it should not be assumed to automatically reduce costs.


Migration Risks

The migration should be treated as a production network change.

Key risks include:

  • Connection resets: AWS warns that converting from a zonal NAT Gateway to a Regional NAT Gateway can reset existing connections.

  • Public egress IP changes: if a Regional NAT Gateway is created with new IPs, external services may see a different source IP.

  • External allowlists: firewalls, partners, APIs, or SaaS services may depend on the current IPs.

  • Route table errors: an incorrectly configured 0.0.0.0/0 route can cut off internet egress from private subnets.

  • Destructive Terraform changes: avoid having a wrapper and an internal module compete for ownership of NAT Gateways, Internet Gateways, or routes.

  • Expansion time for new AZs: AWS documents that expanding the Regional NAT Gateway to a new Availability Zone can take up to 60 minutes after workloads are detected there.


Recommended Migration Strategy

Use a maintenance window and apply the change in a controlled manner.


1. Inventory the Current State

Before modifying Terraform or routes, document:

  • Current NAT Gateway IDs.

  • Current Elastic IPs.

  • Private route tables and their 0.0.0.0/0 routes.

  • Public subnets used exclusively for NAT.

  • External systems that allowlist egress IPs.

  • Traffic volume per NAT Gateway.

  • Whether or not a Private NAT Gateway is being used.


Useful commands:


aws ec2 describe-nat-gateways \
  --filter Name=state,Values=available \
  --query 'NatGateways[].{Id:NatGatewayId,Vpc:VpcId,Subnet:SubnetId,State:State,PublicIps:NatGatewayAddresses[].PublicIp}'
aws ec2 describe-route-tables \
  --query 'RouteTables[].{RouteTableId:RouteTableId,VpcId:VpcId,Routes:Routes[?DestinationCidrBlock==`0.0.0.0/0`]}'

2. Decide Whether to Preserve Egress IPs

AWS documents two migration paths.


If new IPs can be used:

  1. Create a new Regional NAT Gateway.

  2. Update the private route tables to point to the Regional NAT Gateway.

  3. Delete the previous zonal NAT Gateways.


This approach is simpler, but it changes the egress IPs and resets connections when the routes are updated.


If the current IPs must be preserved:

  1. Delete the existing zonal NAT Gateways to release their Elastic IPs.

  2. Create the Regional NAT Gateway using those released IPs.

  3. Update the private route tables to point to the Regional NAT Gateway.


This approach preserves the IPs, but requires a maintenance window because internet egress will be interrupted during the transition.


3. Update the Terraform Provider

The aws_nat_gateway resource supports the availability_mode argument, with zonal and regional values. In regional mode, Terraform uses vpc_id instead of subnet_id.


Use AWS provider >= 6.24.0 if the module depends on this functionality.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 6.24.0"
    }
  }
}

4. Model a Regional NAT Gateway in Terraform

Simplified example using automatic mode:


resource "aws_internet_gateway" "this" {
  vpc_id = aws_vpc.this.id
}

resource "aws_nat_gateway" "regional" {
  vpc_id            = aws_vpc.this.id
  availability_mode = "regional"

  tags = {
    Name = "example-regional-nat"
  }

  depends_on = [aws_internet_gateway.this]
}

The private route tables point to the Regional NAT Gateway:

resource "aws_route" "private_default" {
  for_each = aws_route_table.private

  route_table_id         = each.value.id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.regional.id
}

5. Review the Terraform Plan

Before applying the changes, validate:

  • That the expected Regional NAT Gateway will be created.

  • That the private routes point to the new Regional NAT Gateway.

  • That there are no unexpected replacements of the VPC, subnets, route tables, or Internet Gateway.

  • That the previous zonal NAT Gateways are not destroyed before a functional route is in place, unless the IPs are being preserved.

  • That create_vpc = false or external VPC modes do not attempt to create NAT resources by mistake.

  • That zonal behavior remains the default for backward compatibility.


6. Validate After the Change

Infrastructure checks:

aws ec2 describe-nat-gateways \
  --nat-gateway-ids <nat-gateway-id> \
  --query 'NatGateways[].{Id:NatGatewayId,State:State,Mode:AvailabilityMode,RouteTableId:RouteTableId,Addresses:NatGatewayAddresses}'
aws ec2 describe-route-tables \
  --filters Name=route.nat-gateway-id,Values=<nat-gateway-id> \
  --query 'RouteTables[].RouteTableId'

Functional checks:

  • Private instances or workloads can access the internet.

  • Image pulls, package updates, and calls to external APIs work correctly.

  • External systems see the expected egress IPs.

  • No routing errors appear in private subnets.

  • CloudWatch metrics for the NAT Gateway remain healthy.


Rollback Plan

Prepare the rollback before making the change.


If migrating using new IPs:

  1. Keep the previous zonal NAT Gateways until validation is complete.

  2. If anything fails, restore the route tables to the previous NAT Gateway IDs.

  3. Confirm internet egress from private subnets.

  4. Delete the Regional NAT Gateway only once the rollback has been confirmed.


If migrating while preserving IPs, the rollback is more complex because the zonal NAT Gateways were deleted to release the Elastic IPs. In this case, document the previous topology and plan for an additional interruption if a rollback is required.


Conclusion

Regional NAT Gateways are an important improvement for simplifying internet egress for private workloads in multi-AZ VPCs. They can reduce routing complexity, improve security posture by eliminating the need for public subnets to host the NAT, and delegate high-availability expansion across Availability Zones to AWS.


The change does not eliminate NAT Gateway costs, does not replace Private NAT Gateway, and should not be implemented without reviewing egress IPs, allowlists, routes, the Terraform provider, and the rollback plan.


The practical recommendation is to keep zonal NAT as the default behavior for backward compatibility and offer Regional NAT as an explicit module option for environments that would benefit from a simpler, more standardized architecture.


Official References





Silvio Depetri

Cloud Engineer

 
 
bottom of page
window.addEventListener('load', function() {   var search = window.location.search;   if (!search || search === '?') return;   var params = search.slice(1);   setTimeout(function() {     document.querySelectorAll('iframe').forEach(function(fr) {       try { fr.contentWindow.postMessage({type:'TERACLOUD_UTM', params: params}, '*'); } catch(e) {}     });   }, 1500); });