EC2 No Internet Access in Custom VPC: Fix Internet Gateway and Route Table

EC2 no internet access in a custom VPC is one of the most common launch-day failures — the instance is running, but outbound connectivity is completely silent.

TL;DR: EC2 No Internet Access — Fix Checklist

Step What to Do Why It Matters Complexity
1 Create and attach an Internet Gateway to the VPC Without an IGW, the VPC has no path to the public internet Low
2 Add a default route (0.0.0.0/0) pointing to the IGW in the subnet's route table The IGW exists but traffic has no route to reach it Low
3 Assign a public IP or Elastic IP to the instance Private IPs are not routable on the public internet Low
4 Verify Security Group allows outbound traffic A restrictive outbound rule silently drops all egress Low
5 Verify Network ACL allows inbound and outbound traffic NACLs are stateless — return traffic must be explicitly allowed Medium

Why EC2 Loses Internet Access in a Custom VPC

When you use the default VPC, AWS pre-wires the Internet Gateway, route tables, and public IP assignment for you. A custom VPC ships with none of that. Every connectivity component must be explicitly created and associated — and each missing piece produces the same symptom: the instance is reachable from within the VPC but completely dark to the outside world.

Most engineers check the instance state first. It's running, the status checks pass, and SSH works from a bastion. The assumption becomes "the instance is fine, something must be wrong with DNS." They swap resolvers, restart systemd-resolved, and lose 30 minutes before realizing the problem is upstream of the OS entirely.

The actual failure is architectural, not operational. No route exists between the subnet and the internet.

How Traffic Flows — and Where It Breaks

graph LR EC2[EC2 Instance] --> RT{Route Table} RT -- No 0.0.0.0/0 route --> DROP1[Packet Dropped] RT -- 0.0.0.0/0 via IGW --> IGW[Internet Gateway] IGW -- No Public IP --> DROP2[Cannot Forward] IGW -- Public IP exists --> SG{Security Group} SG -- Outbound blocked --> DROP3[Egress Blocked] SG -- Outbound allowed --> NACL{Network ACL} NACL -- Rule denies --> DROP4[Subnet Blocked] NACL -- Rule allows --> INET[Internet]
  1. EC2 Instance generates outbound traffic (e.g., ping to 8.8.8.8).
  2. The packet hits the subnet's route table. If no 0.0.0.0/0 route exists, the packet is dropped here — it never leaves the subnet.
  3. If the route exists and points to the Internet Gateway (IGW), the packet reaches the IGW.
  4. The IGW performs NAT using the instance's public IP. If no public IP is assigned, the IGW cannot forward the packet.
  5. The Security Group (stateful instance-level firewall) evaluates outbound rules. Default SGs allow all egress, but custom SGs may not.
  6. The Network ACL (stateless subnet-level filter) evaluates both outbound and inbound rules. Return traffic requires an explicit allow on ephemeral ports.
Analogy: Think of the IGW as the building's front door, the route table as the lobby directory, the public IP as your unit number on the mailbox, the Security Group as your apartment door lock, and the Network ACL as the building's front desk guard who checks both arrivals and departures independently. All five must cooperate for a letter to reach you from outside.

Step 1: Create and Attach an Internet Gateway

An Internet Gateway is a horizontally scaled, redundant VPC component. One IGW per VPC is the documented limit. If your VPC has no IGW, no amount of route table configuration will produce internet connectivity.

🔽 CLI: Create and attach an Internet Gateway
# Create the Internet Gateway
aws ec2 create-internet-gateway \
  --query 'InternetGateway.InternetGatewayId' \
  --output text

# Attach it to your VPC (replace with your actual IDs)
aws ec2 attach-internet-gateway \
  --internet-gateway-id igw-0abc1234def567890 \
  --vpc-id vpc-0abc1234def567890

# Verify the attachment state is "available"
aws ec2 describe-internet-gateways \
  --internet-gateway-ids igw-0abc1234def567890
  

Confirm the Attachments array in the response shows State: available and the correct VPC ID. An IGW in detached state is functionally equivalent to no IGW at all.

Step 2: Add a Default Route to the Route Table

Attaching the IGW does not automatically route traffic through it. The subnet's route table must have an explicit entry: destination 0.0.0.0/0 targeting the IGW. Without this, the route table only knows about local VPC CIDR traffic.

🔽 CLI: Add the default route and associate the route table
# Find the route table associated with your public subnet
aws ec2 describe-route-tables \
  --filters Name=association.subnet-id,Values=subnet-0abc1234def567890

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

# If the subnet is not yet associated with this route table:
aws ec2 associate-route-table \
  --route-table-id rtb-0abc1234def567890 \
  --subnet-id subnet-0abc1234def567890
  

A subnet without an explicit route table association uses the VPC's main route table. If the main route table lacks the IGW route, traffic from that subnet has no internet path regardless of what other route tables exist in the VPC.

The route table is the lobby directory. If "outside world" isn't listed, the packet never leaves the building.

Step 3: Assign a Public IP to the EC2 Instance

Even with a working IGW and correct route, the IGW cannot forward traffic for an instance that has only a private IP. The IGW maps the instance's public IP to its private IP during transit — no public IP means no mapping, no egress.

For instances already running without a public IP, assign an Elastic IP:

🔽 CLI: Allocate and associate an Elastic IP
# Allocate an Elastic IP from Amazon's pool
aws ec2 allocate-address \
  --domain vpc \
  --query 'AllocationId' \
  --output text

# Associate it with the running instance
aws ec2 associate-address \
  --instance-id i-0abc1234def567890 \
  --allocation-id eipalloc-0abc1234def567890
  

For future instances, enable auto-assign public IP at the subnet level so every instance launched into that subnet receives a public IP automatically:

aws ec2 modify-subnet-attribute \
  --subnet-id subnet-0abc1234def567890 \
  --map-public-ip-on-launch

Step 4: Verify Security Group Outbound Rules

Security Groups (stateful instance-level firewalls) track connection state, so return traffic for allowed outbound connections is automatically permitted. The risk is a custom Security Group that was created with a restrictive or empty outbound ruleset.

aws ec2 describe-security-groups \
  --group-ids sg-0abc1234def567890 \
  --query 'SecurityGroups[*].IpPermissionsEgress'

A healthy public instance should have at minimum an outbound rule permitting all traffic (0.0.0.0/0, protocol -1), or at least TCP 443 and TCP 80 if you prefer tighter egress control. If the output is an empty array, all outbound traffic is blocked.

Step 5: Verify Network ACL Rules — the Stateless Layer

This is where engineers who fixed steps 1–4 still get stuck. Unlike Security Groups, Network ACLs (stateless subnet-level filters) evaluate inbound and outbound traffic independently. Return traffic from an outbound connection is not automatically allowed — it must match an explicit inbound rule covering the ephemeral port range the OS selected for the connection.

graph LR OUT[EC2 Outbound Packet] --> NACL_OUT{NACL Outbound Rule} NACL_OUT -- Deny --> DROP_OUT[Dropped] NACL_OUT -- Allow --> INET[Internet] INET --> RETURN[Return Packet] RETURN --> NACL_IN{NACL Inbound Rule} NACL_IN -- No ephemeral port rule --> DROP_IN[Return Dropped] NACL_IN -- Allow --> EC2[EC2 Instance]
🔽 CLI: Inspect Network ACL rules for your subnet
# Find the NACL associated with your subnet
aws ec2 describe-network-acls \
  --filters Name=association.subnet-id,Values=subnet-0abc1234def567890
  

Check both Entries where Egress: true (outbound) and Egress: false (inbound). The default NACL created with a custom VPC allows all traffic in both directions. A custom NACL starts with a DENY ALL rule — if your subnet was associated with a custom NACL without adding explicit allow rules, all traffic is silently dropped at the subnet boundary before it ever reaches the Security Group.

Custom NACLs with a DENY ALL default are the most common silent killer in this troubleshooting sequence.

EC2 No Internet Access: Full Diagnostic Flow

graph LR START([No Internet Access]) --> Q1{IGW attached?} Q1 -- No --> FIX1[Create and attach IGW] Q1 -- Yes --> Q2{0.0.0.0/0 route exists?} FIX1 --> Q2 Q2 -- No --> FIX2[Add route to route table] Q2 -- Yes --> Q3{Subnet associated?} FIX2 --> Q3 Q3 -- No --> FIX3[Associate subnet to route table] Q3 -- Yes --> Q4{Public IP assigned?} FIX3 --> Q4 Q4 -- No --> FIX4[Allocate and attach Elastic IP] Q4 -- Yes --> Q5{SG egress open?} FIX4 --> Q5 Q5 -- No --> FIX5[Add outbound SG rule] Q5 -- Yes --> Q6{NACL allows both dirs?} FIX5 --> Q6 Q6 -- No --> FIX6[Add NACL inbound and outbound rules] Q6 -- Yes --> DONE([Internet Access Restored]) FIX6 --> DONE
  1. Start by confirming whether an IGW exists and is attached — this is the most common root cause in new custom VPCs.
  2. Check the route table for the 0.0.0.0/0 → IGW entry before touching anything else.
  3. Verify the instance has a public or Elastic IP — a private-only instance cannot reach the internet through an IGW.
  4. Inspect Security Group egress rules — an empty outbound ruleset blocks all traffic silently.
  5. Check the NACL last, but check it thoroughly — verify both outbound and inbound entries, including ephemeral port ranges for return traffic.

Production Gotcha: The Main Route Table Trap

In practice, teams often create a dedicated public route table with the IGW route, add the route correctly, but forget to explicitly associate the public subnet with that route table. The subnet silently falls back to the VPC's main route table, which has no IGW route. The console shows the route table exists and looks correct — but it's not the one the subnet is actually using.

Always verify the association, not just the route table content:

aws ec2 describe-route-tables \
  --filters Name=association.subnet-id,Values=subnet-0abc1234def567890 \
  --query 'RouteTables[*].RouteTableId'

If this returns empty or returns the main route table ID instead of your public route table, the association is missing — not the route.

Wrap-Up: Restoring EC2 Internet Access in a Custom VPC

Every layer in this stack is independently required. Fixing four out of five produces the same symptom as fixing none. The correct mental model is a checklist, not a single fix — IGW attached, route table updated, public IP assigned, Security Group egress open, NACL rules permitting both directions.

For teams building custom VPCs repeatedly, encoding this configuration in Infrastructure as Code (AWS CloudFormation or Terraform) eliminates the manual association errors that cause most of these failures in the first place.

Glossary

Term Definition
Internet Gateway (IGW) A VPC component that enables communication between instances in the VPC and the public internet. One IGW can be attached per VPC.
Route Table A set of rules (routes) that determine where network traffic from a subnet is directed. Each subnet must be associated with exactly one route table.
Elastic IP (EIP) A static public IPv4 address allocated from AWS's pool and associated with an instance or network interface in a VPC.
Network ACL (NACL) A stateless subnet-level firewall that evaluates inbound and outbound traffic independently using numbered allow/deny rules.
Security Group A stateful instance-level firewall that tracks connection state, automatically allowing return traffic for permitted outbound connections.

Related Posts

Comments

Popular posts from this blog

EC2 SSH Connection Timeout: Which Security Group Rules to Check

Difference Between IAM User and IAM Role: Which One Should Your EC2 Use?