How to Force Specific Traffic Through NAT Gateway While Maintaining Internet Access in AWS VPC Routing


8 views

When working with AWS VPC routing tables, we often encounter scenarios where we need fine-grained control over traffic paths. A common requirement is routing specific external traffic through a NAT Gateway while maintaining direct internet access via an Internet Gateway (IGW) for other traffic.

AWS automatically sorts route table entries by prefix length (from most to least specific) when saved. This explains why your manually entered order gets rearranged:

# What AWS automatically does:
172.31.0.0/16   local
0.0.0.0/0       igw-b4ac67d0
A.B.C.D/32      nat-451b3be9

The issue here is that the most specific route (/32) should take precedence over the default route (/0), but AWS's automatic sorting makes this impossible in a single route table.

The proper AWS way to achieve this is by using separate route tables for different subnets:

# Public subnet route table
172.31.0.0/16   local
0.0.0.0/0       igw-b4ac67d0

# Private subnet route table 
172.31.0.0/16   local
A.B.C.D/32      nat-451b3be9
0.0.0.0/0       nat-451b3be9

Here's how to set this up properly:

# Create a new private subnet
aws ec2 create-subnet --vpc-id vpc-123456 --cidr-block 172.31.1.0/24

# Create a new route table for private subnet
aws ec2 create-route-table --vpc-id vpc-123456

# Add specific route before the default route
aws ec2 create-route --route-table-id rtb-7890123 \
--destination-cidr-block A.B.C.D/32 \
--nat-gateway-id nat-451b3be9

aws ec2 create-route --route-table-id rtb-7890123 \
--destination-cidr-block 0.0.0.0/0 \
--nat-gateway-id nat-451b3be9

# Associate route table with private subnet
aws ec2 associate-route-table --subnet-id subnet-456789 \
--route-table-id rtb-7890123

If separating subnets isn't feasible, you can use Network ACLs to control traffic flow:

# Create network ACL entry for specific IP
aws ec2 create-network-acl-entry \
--network-acl-id acl-123456 \
--rule-number 100 \
--protocol -1 \
--rule-action deny \
--cidr-block A.B.C.D/32 \
--egress
  • NAT Gateway must be in a public subnet with IGW access
  • Each NAT Gateway incurs hourly costs + data processing fees
  • Consider using AWS PrivateLink for accessing specific services
  • For multiple instances, a NAT Gateway is more cost-effective than Elastic IPs

Use these commands to verify your setup:

# Check route table associations
aws ec2 describe-route-tables --route-table-ids rtb-123456

# Test connectivity
aws ec2-instance-connect send-ssh-public-key \
--instance-id i-123456 \
--availability-zone us-east-1a \
--instance-os-user ec2-user \
--ssh-public-key file://my_key.pub

When working with AWS VPC networking, we often encounter scenarios requiring both Internet Gateway (IGW) and NAT Gateway coexistence. The specific case involves:

  • A VPC with CIDR 172.31.0.0/16
  • EC2 instances with public Elastic IPs
  • Need to route specific external traffic (A.B.C.D/32) through NAT
  • All other traffic should use IGW

AWS evaluates routes based on most specific match first principle. The desired behavior requires:

172.31.0.0/16   local
A.B.C.D/32      nat-451b3be9
0.0.0.0/0       igw-b4ac67d0

But AWS Console UI forces this order:

172.31.0.0/16   local
0.0.0.0/0       igw-b4ac67d0
A.B.C.D/32      nat-451b3be9

The AWS Management Console's route table editor has limitations. For precise control, use AWS CLI:

# First, delete the default route (if exists)
aws ec2 delete-route \
  --route-table-id rtb-1234567890 \
  --destination-cidr-block 0.0.0.0/0

# Then add routes in exact order
aws ec2 create-route \
  --route-table-id rtb-1234567890 \
  --destination-cidr-block A.B.C.D/32 \
  --nat-gateway-id nat-451b3be9

aws ec2 create-route \
  --route-table-id rtb-1234567890 \
  --destination-cidr-block 0.0.0.0/0 \
  --gateway-id igw-b4ac67d0

After applying the routes, verify with:

aws ec2 describe-route-tables \
  --route-table-ids rtb-1234567890 \
  --query 'RouteTables[0].Routes'

Test connectivity from your EC2 instance:

# Should show NAT gateway's public IP
curl ifconfig.me --connect-to A.B.C.D:80

# Other traffic should use instance's Elastic IP
curl ifconfig.me

For more complex scenarios, consider:

  1. Public subnet with IGW (for instances needing direct internet access)
  2. Private subnet with NAT Gateway (for instances needing outbound-only access)

Example Terraform configuration:

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id
  
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }
}

resource "aws_route_table" "private" {
  vpc_id = aws_vpc.main.id
  
  route {
    cidr_block = "A.B.C.D/32"
    nat_gateway_id = aws_nat_gateway.nat.id
  }
  
  route {
    cidr_block = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.nat.id
  }
}

If routes aren't working as expected:

  • Check NACLs and Security Groups for both NAT and EC2 instances
  • Verify NAT Gateway is in a public subnet with IGW access
  • Confirm NAT Gateway has allocated Elastic IP
  • Ensure route table is properly associated with the subnet