EC2 No Internet Access in a Custom VPC: Attaching an IGW and Fixing Route Tables

TL;DR

Launching an EC2 instance in a custom VPC public subnet without internet access almost always comes down to three missing pieces. Fix all three and traffic flows.

Missing ComponentSymptomFix
Internet Gateway (IGW)No outbound route existsCreate & attach IGW to VPC
Route Table Entry0.0.0.0/0 has no targetAdd route: 0.0.0.0/0 → IGW
Public IP / Elastic IPInstance has no routable addressEnable auto-assign public IP or attach EIP

Why This Happens: The Architecture Logic

AWS does not wire internet connectivity automatically when you create a custom VPC. The default VPC comes pre-configured with an IGW and a default route, which is why beginners never hit this wall there. The moment you create a custom VPC, you own the entire network stack.

Think of it like a new office building. The building (VPC) has rooms (subnets) and internal hallways (local routes). But until the city connects a road to the building's front door (IGW) and you post a sign saying "all outbound traffic uses this road" (route table entry), no one inside can reach the outside world — regardless of how "public" the room label says it is.

Data Flow: Before vs. After the Fix

BEFORE (broken state):

EC2 Instance
  └─► Subnet Route Table
        ├─ 10.0.0.0/16 → local   ✅ (intra-VPC works)
        └─ (no default route)    ❌ (internet traffic dropped)

AFTER (working state):

EC2 Instance (with Public IP)
  └─► Subnet Route Table
        ├─ 10.0.0.0/16  → local        ✅
        └─ 0.0.0.0/0    → igw-xxxxxxxx ✅
              └─► Internet Gateway
                    └─► Public Internet (google.com)

Step-by-Step Fix: AWS CLI (Least Privilege, No Console)

Execute these commands in order. Replace placeholder values with your own resource IDs.

Step 1 — Create and Attach the Internet Gateway

# Create the IGW
aws ec2 create-internet-gateway \
  --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=my-igw}]' \
  --query 'InternetGateway.InternetGatewayId' \
  --output text
# Output: igw-0abc123def456

# Attach it to your VPC
aws ec2 attach-internet-gateway \
  --internet-gateway-id igw-0abc123def456 \
  --vpc-id vpc-0123456789abcdef0

Step 2 — Add the Default Route to the Route Table

# Find the route table associated with your public subnet
aws ec2 describe-route-tables \
  --filters "Name=association.subnet-id,Values=subnet-0abc123" \
  --query 'RouteTables[*].RouteTableId' \
  --output text
# Output: rtb-0abc123def

# Add the 0.0.0.0/0 route pointing to the IGW
aws ec2 create-route \
  --route-table-id rtb-0abc123def \
  --destination-cidr-block 0.0.0.0/0 \
  --gateway-id igw-0abc123def456

Step 3 — Ensure the EC2 Instance Has a Public IP

# Option A: Allocate and associate an Elastic IP (recommended for production)
aws ec2 allocate-address --domain vpc
# Output: AllocationId = eipalloc-0abc123

aws ec2 associate-address \
  --instance-id i-0abc123def456 \
  --allocation-id eipalloc-0abc123

# Option B: Enable auto-assign public IP on the subnet (dev/test)
aws ec2 modify-subnet-attribute \
  --subnet-id subnet-0abc123 \
  --map-public-ip-on-launch

Step 4 — Verify Security Group Allows ICMP (for ping)

# Allow outbound ICMP (ping to google.com requires this)
aws ec2 authorize-security-group-egress \
  --group-id sg-0abc123 \
  --protocol icmp \
  --port -1 \
  --cidr 0.0.0.0/0

# If testing inbound ping TO the instance:
aws ec2 authorize-security-group-ingress \
  --group-id sg-0abc123 \
  --protocol icmp \
  --port -1 \
  --cidr 0.0.0.0/0

Terraform Equivalent (IaC-First Teams)

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  tags   = { Name = "main-igw" }
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }

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

resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

IAM: Minimum Required Permissions

Apply least-privilege. The IAM principal executing these commands needs only:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:CreateInternetGateway",
        "ec2:AttachInternetGateway",
        "ec2:DescribeRouteTables",
        "ec2:CreateRoute",
        "ec2:AllocateAddress",
        "ec2:AssociateAddress",
        "ec2:ModifySubnetAttribute",
        "ec2:AuthorizeSecurityGroupEgress",
        "ec2:AuthorizeSecurityGroupIngress",
        "ec2:CreateTags"
      ],
      "Resource": "*"
    }
  ]
}

Scope Resource to specific VPC ARNs in production to tighten this further.

Cost Impact

  • Internet Gateway: No hourly charge for the IGW itself. You pay for data transfer out at ~$0.09/GB (varies by region). Inbound data is free.
  • Elastic IP: Free while associated with a running instance. Charged at ~$0.005/hr when allocated but not associated — release unused EIPs immediately.
  • NAT Gateway (not used here): If your use case later involves private subnets, a NAT Gateway costs ~$0.045/hr + data processing fees — a common cost surprise for teams scaling up.

Glossary

TermDefinition
Internet Gateway (IGW)A horizontally scaled, redundant VPC component that enables communication between instances in your VPC and the public internet.
Route TableA set of rules (routes) that determine where network traffic from a subnet or gateway is directed.
Public SubnetA subnet whose associated route table contains a route to an Internet Gateway, making resources reachable from the internet (given a public IP).
Elastic IP (EIP)A static, public IPv4 address allocated to your AWS account that can be dynamically remapped to any instance in your VPC.
Security GroupA stateful virtual firewall controlling inbound and outbound traffic at the instance level using allow-only rules.

Comments

Popular posts from this blog

Breaking the Loop: How to Prevent Recursive Lambda Triggers on S3

EC2 SSH 'Connection Timed Out': The Definitive Security Group Diagnosis Guide