LAB20_AWS_NAT Gateway and Elastic IP for Private Subnet Egress

NAT Gateway and Elastic IP for Private Subnet Egress

☁ AWS ⭐ Beginner NAT Gateway Elastic IP Private Route Table Outbound Internet

Scenario

Your private subnet has no internet access. EC2 instances and ECS tasks in private subnets need to make outbound calls — to download package updates, call AWS APIs, or reach external services — but must never be reachable from the internet directly.

A NAT Gateway in the public subnet allows one-way outbound internet access for private resources without exposing them to inbound connections.

Your Objectives
  • Allocate an aws_eip (Elastic IP) for the NAT Gateway.
  • Create an aws_nat_gateway in the public subnet, using the EIP.
  • Create a private route table with a default route (0.0.0.0/0) sending traffic to the NAT Gateway.
  • Associate the private route table with the private subnet.
  • Output the NAT Gateway ID and the EIP public IP.

Additional Context

NAT Gateways are placed in a public subnet (one with an IGW route), not in the private subnet. Traffic from private instances flows: Private Subnet → NAT Gateway (public subnet) → Internet Gateway → Internet. The NAT Gateway performs source address translation, replacing the private IP with the EIP.

NAT Gateways cost approximately $0.045/hour plus $0.045 per GB processed. For cost-sensitive scenarios, consider a NAT Instance (EC2 with IP forwarding) or VPC Endpoints (preferred for AWS service traffic, as they bypass the NAT entirely).

versions.tf

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = { source = "hashicorp/aws", version = "~> 5.0" }
  }
}

variables.tf

variable "aws_region"       { type = string; default = "us-east-1" }
variable "vpc_id"           { type = string; description = "ID of the VPC containing the subnets." }
variable "public_subnet_id" { type = string; description = "Public subnet to place the NAT Gateway in." }
variable "private_subnet_id"{ type = string; description = "Private subnet that needs outbound internet." }
variable "environment"      { type = string; default = "dev" }

main.tf

# main.tf

provider "aws" {
  region = var.aws_region
}

# Elastic IP — a static public IP address for the NAT Gateway.
# The 'vpc' domain is required for VPC-associated EIPs.
resource "aws_eip" "nat" {
  domain = "vpc"

  tags = { Name = "nat-eip-${var.environment}", Environment = var.environment, ManagedBy = "terraform" }
}

# NAT Gateway — allows private resources to initiate outbound connections.
# Must be placed in a PUBLIC subnet (one with an IGW route).
# It uses the EIP as its external address.
resource "aws_nat_gateway" "main" {
  allocation_id = aws_eip.nat.id
  subnet_id     = var.public_subnet_id   # public subnet — NOT the private subnet

  tags = { Name = "main-nat-${var.environment}", Environment = var.environment, ManagedBy = "terraform" }

  # NAT Gateway must be created after the IGW to ensure the subnet's internet route exists.
  # In practice, Terraform usually handles this ordering, but depends_on is explicit.
}

# Private route table — routes all outbound traffic through the NAT Gateway.
resource "aws_route_table" "private" {
  vpc_id = var.vpc_id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.main.id   # outbound to NAT, not IGW
  }

  tags = { Name = "private-rt-${var.environment}", Environment = var.environment, ManagedBy = "terraform" }
}

# Associate the private route table with the private subnet.
resource "aws_route_table_association" "private" {
  subnet_id      = var.private_subnet_id
  route_table_id = aws_route_table.private.id
}

outputs.tf

output "nat_gateway_id"  { value = aws_nat_gateway.main.id }
output "nat_eip"         { value = aws_eip.nat.public_ip }
output "private_route_table_id" { value = aws_route_table.private.id }

Workflow Commands

terraform init
terraform apply \
  -var="vpc_id=vpc-xxxx" \
  -var="public_subnet_id=subnet-public" \
  -var="private_subnet_id=subnet-private"

# Verify: launch an EC2 in the private subnet and test outbound:
# (from EC2 in private subnet)
curl -s https://ifconfig.me  # should return the NAT EIP address

terraform destroy -var="vpc_id=..." -var="public_subnet_id=..." -var="private_subnet_id=..."
✓ Expected Output After Apply
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.

Outputs:

nat_eip              = "54.201.123.45"
nat_gateway_id       = "nat-0abcdef1234567890"
private_route_table_id = "rtb-0123456789abcdef0"