What is a NAT Gateway?

NAT Gateway enables instances in a private subnet to connect to the Internet, but prevent the internet from initiating a connection with those instances.

Private vs Public subnet

The instances in the public subnet can send outbound traffic directly to the Internet via Internet Gateway (IGW), whereas the instances in the private subnet can't. Instead, the instances in the private subnet can access the Internet by using a NAT gateway that resides in the public subnet.

Typical example is a public-facing web application (public subnet), while maintaining back-end servers (private subnets) that aren't publicly accessible.

diagram - public vs private subnet

As per the above diagram, instances in public subnet have route to the Internet via IGW. Instances in the private subnet have route to the Internet via NAT gateway. Users in the Internet can also initiate inbound connections to the instances in the public subnet using their public/elastic IP.

How does NAT gateway work?

To create a NAT gateway, you must specify the public subnet in which the NAT gateway should reside. An elastic IP address has to be associated with a NAT gateway when it is created. Each NAT gateway is created in a specific Availability Zone and implemented with redundancy in that zone. As you can see above, the NAT gateway was created in AZ-1/public subnet 1a.

You can not create a NAT gateway without having an IGW attached to the VPC. The traffic flow would be Instance >> NAT Gateway >> IGW >> Internet.

NAT Gateway HA scenario

NAT Gateway is Highly Available in one Availability Zone, If you have resources in multiple Availability Zones and they share one NAT gateway, and if the NAT gateway’s Availability Zone is down, resources in the other Availability Zones lose Internet access.

In our example above, if AZ 1a goes down,  instances in other AZs lose Internet Access.

Depending on your business requirement and  fault-tolerant architecture, make sure to create NAT Gateways in at least two Availability Zones.

3 x NAT Gateway

As you can see above, now we have NAT Gateways in each AZ which provide fault-tolerance against AZ failures.

NAT Gateway HA deployment using Terraform

Suresh-MacBook:NAT HA suresh$ tree
.
|-- main.tf
|-- terraform.tfstate
|-- terraform.tfstate.backup
|-- vars.tf
`-- vpc-subnets.tf

0 directories, 5 files
provider "aws" {
  region  = var.region
  profile = "suresh-stage"
}
main.tf
variable "region" {
  default = "eu-west-2"
}

variable "vpc-cidr" {
  default = "10.10.0.0/16"
}

variable "azs" {
  type = list
  default = ["eu-west-2a" , "eu-west-2b", "eu-west-2c"]
}

variable "private-subnets" {
  type = list
  default = ["10.10.1.0/24" , "10.10.2.0/24" , "10.10.3.0/24"]
}
variable "public-subnets" {
  type = list
  default = ["10.10.20.0/24" , "10.10.21.0/24" , "10.10.22.0/24"]
}
vars.tf
#Create VPC
resource "aws_vpc" "test-vpc" {
  cidr_block       = var.vpc-cidr

  tags = {
    Name = "test-vpc"
  }
}

#Create private subnets
resource "aws_subnet" "private-subnets" {
  vpc_id = aws_vpc.test-vpc.id
  count = length(var.azs)
  cidr_block = element(var.private-subnets , count.index)
  availability_zone = element(var.azs , count.index)

  tags = {
    Name = "private-subnet-${count.index+1}"
  }
}

#Create public subnets
resource "aws_subnet" "public-subnets" {
  vpc_id = aws_vpc.test-vpc.id
  count = length(var.azs)
  cidr_block = element(var.public-subnets , count.index)
  availability_zone = element(var.azs , count.index)

  tags = {
    Name = "public-subnet-${count.index+1}"
  }
}


#Create IGW
resource "aws_internet_gateway" "test-igw" {
  vpc_id = aws_vpc.test-vpc.id

  tags = {
    Name = "test-igw"
  }
}

#Single route table for public subnet
resource "aws_route_table" "public-rtable" {
  vpc_id = aws_vpc.test-vpc.id

  tags = {
    Name = "public-rtable"
  }
}

#add routes to public-rtable
resource "aws_route" "public-rtable" {
  route_table_id            = aws_route_table.public-rtable.id
  destination_cidr_block    = "0.0.0.0/0"
  gateway_id                = aws_internet_gateway.test-igw.id
}

#route table association public subnets
resource "aws_route_table_association" "public-subnet-association" {
  count          = length(var.public-subnets)
  subnet_id      = element(aws_subnet.public-subnets.*.id , count.index)
  route_table_id = aws_route_table.public-rtable.id
}

#private route tables
resource "aws_route_table" "private-rtable" {
  count          = length(var.private-subnets)
  vpc_id         = aws_vpc.test-vpc.id

  tags = {
    Name = "private-rtable-${count.index+1}"
  }
}

#route table association private subnets
resource "aws_route_table_association" "private-subnet-association" {
  count          = length(var.private-subnets)
  subnet_id      = element(aws_subnet.private-subnets.*.id , count.index)
  route_table_id = element(aws_route_table.private-rtable.*.id , count.index)
}

#3 x EIP
resource "aws_eip" "nat-eip" {
  count    = length(var.azs)
  vpc      = true

  tags = {
    Name = "EIP--${count.index+1}"
  }
}

#3 x NAT gateways
resource "aws_nat_gateway" "prod-nat-gateway" {
  count         = length(var.azs)
  allocation_id = element(aws_eip.nat-eip.*.id , count.index)
  subnet_id     = element(aws_subnet.public-subnets.*.id , count.index)

  tags = {
    Name = "NAT-GW--${count.index+1}"
  }
}

#add routes to private-rtable
resource "aws_route" "subnets-private-rtable" {
  count                     = length(var.azs)
  route_table_id            = element(aws_route_table.private-rtable.*.id , count.index)
  destination_cidr_block    = "0.0.0.0/0"
  nat_gateway_id            = element(aws_nat_gateway.prod-nat-gateway.*.id, count.index)
}
vpc-subnets.tf

Verification

private and public subnets
route tables
3 x NAT Gateways
public route table

NAT GW 1

As you can see above, private subnet in AZ-1 has route to the Internet via the NAT Gateway in the same AZ.

NAT GW 2
NAT GW 3

Reference

NAT gateways - Amazon Virtual Private Cloud
Use a NAT gateway in a public VPC subnet to enable outbound internet traffic from instances in a private subnet.

Thanks for reading

As always, your feedback and comments are more than welcome.