Skip to content

Commit 4d37b2b

Browse files
fix(nat): ensure NAT gateways are created in correct availability zones (#1257)
1 parent b3fb14f commit 4d37b2b

File tree

3 files changed

+59
-4
lines changed

3 files changed

+59
-4
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [Unreleased]
6+
7+
### Bug Fixes
8+
9+
* fix: Ensure NAT gateways are created in the correct availability zones ([#1257](https://github.com/terraform-aws-modules/terraform-aws-vpc/issues/1257))
10+
511
## [6.5.0](https://github.com/terraform-aws-modules/terraform-aws-vpc/compare/v6.4.1...v6.5.0) (2025-10-21)
612

713
### Features

main.tf

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,9 +1199,33 @@ resource "aws_route" "private_ipv6_egress" {
11991199
# NAT Gateway
12001200
################################################################################
12011201

1202+
data "aws_availability_zones" "available" {
1203+
count = length(var.azs) == 0 ? 1 : 0
1204+
1205+
state = "available"
1206+
}
1207+
12021208
locals {
1203-
nat_gateway_count = var.single_nat_gateway ? 1 : var.one_nat_gateway_per_az ? length(var.azs) : local.max_subnet_length
1204-
nat_gateway_ips = var.reuse_nat_ips ? var.external_nat_ip_ids : aws_eip.nat[*].id
1209+
# Use provided AZs or fetch from data source, then sort for consistency
1210+
sorted_azs = sort(length(var.azs) > 0 ? var.azs : data.aws_availability_zones.available[0].names)
1211+
1212+
# Build mapping AZ -> first public subnet ID found for that AZ
1213+
az_to_public_subnet = {
1214+
for az in local.sorted_azs : az => try(
1215+
# Sort subnets within each AZ for consistent selection
1216+
sort([for s in aws_subnet.public : s.id if s.availability_zone == az])[0],
1217+
aws_subnet.public[0].id
1218+
)
1219+
}
1220+
1221+
nat_gateway_count = var.single_nat_gateway ? 1 : var.one_nat_gateway_per_az ? length(local.sorted_azs) : local.max_subnet_length
1222+
nat_gateway_ips = var.reuse_nat_ips ? sort(var.external_nat_ip_ids) : aws_eip.nat[*].id
1223+
1224+
# Determine NAT gateway subnet IDs to use
1225+
nat_gateway_subnet_ids = length(var.nat_gateway_subnet_ids) > 0 ? sort(var.nat_gateway_subnet_ids) : [
1226+
for i, az in local.sorted_azs : local.az_to_public_subnet[az]
1227+
if i < local.nat_gateway_count
1228+
]
12051229
}
12061230

12071231
resource "aws_eip" "nat" {
@@ -1235,8 +1259,8 @@ resource "aws_nat_gateway" "this" {
12351259
var.single_nat_gateway ? 0 : count.index,
12361260
)
12371261
subnet_id = element(
1238-
aws_subnet.public[*].id,
1239-
var.single_nat_gateway ? 0 : count.index,
1262+
local.nat_gateway_subnet_ids,
1263+
var.single_nat_gateway ? 0 : count.index
12401264
)
12411265

12421266
tags = merge(

variables.tf

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,6 +1210,17 @@ variable "igw_tags" {
12101210
# NAT Gateway
12111211
################################################################################
12121212

1213+
variable "nat_gateway_subnet_ids" {
1214+
description = "List of subnet IDs to use for NAT Gateways. If provided, these subnet IDs will be used (in order). If empty, the module will automatically select the public subnet that matches each Availability Zone (AZ)."
1215+
type = list(string)
1216+
default = []
1217+
1218+
validation {
1219+
condition = alltrue([for id in var.nat_gateway_subnet_ids : can(regex("^subnet-", id)) || id == ""])
1220+
error_message = "NAT Gateway subnet IDs must be valid subnet IDs starting with 'subnet-' or be empty strings."
1221+
}
1222+
}
1223+
12131224
variable "enable_nat_gateway" {
12141225
description = "Should be true if you want to provision NAT Gateways for each of your private networks"
12151226
type = bool
@@ -1234,6 +1245,20 @@ variable "one_nat_gateway_per_az" {
12341245
default = false
12351246
}
12361247

1248+
variable "nat_gateway_subnet_ids" {
1249+
description = <<EOT
1250+
Optional list of subnet IDs to use for NAT Gateways. If provided, these
1251+
subnet IDs will be used (in order). If empty, the module will automatically
1252+
select the public subnet that matches each Availability Zone (AZ).
1253+
EOT
1254+
validation {
1255+
condition = alltrue([for id in var.nat_gateway_subnet_ids : id != ""])
1256+
error_message = "nat_gateway_subnet_ids must not contain empty strings."
1257+
}
1258+
type = list(string)
1259+
default = []
1260+
}
1261+
12371262
variable "reuse_nat_ips" {
12381263
description = "Should be true if you don't want EIPs to be created for your NAT Gateways and will instead pass them in via the 'external_nat_ip_ids' variable"
12391264
type = bool

0 commit comments

Comments
 (0)