Stop Guessing, The AWS Networking Guide I Wish I’d Had: How Internet Gateways, Route Tables, NACLs and Security Groups Really Work Together
Internet gateways, route tables, NACLs and security groups explained: how the four fit together to keep your AWS VMs reachable and safe
Your EC2 instance is running. The security group looks right. And you still can’t connect.
So which of four things is blocking you? The internet gateway, the route table, the network ACL, or the security group? Most people can name all four, but few can say in what order they are checked, or which ones remember a connection and which ones forget it. That gap is where hours of debugging disappear.
This article closes it. We’ll look at what each component is, what it does for your public and private VMs, how they differ, when to reach for each one, and how a single packet passes through all of them. By the end you’ll be able to design a secure three-tier setup and troubleshoot a dead connection in a sensible order.
One analogy will carry us through: a building. The VPC is the building, each subnet is a floor, and each of our four components plays a different security role. And if you’re wondering how private servers get updates and admin access, I covered bastion hosts and NAT gateways in an earlier article, linked at the end.
In this article
- The big picture: one VPC, four building blocks
- Internet gateway: the front door
- Route tables: the building directory
- From routing to filtering: two checkpoints
- Network ACLs (NACL): the gate at each floor
- Security groups: the guard at every office door
- Stateful vs. stateless: the difference behind most headaches
- How they work together: the journey of a packet
- Side by side: the core differences
- Which one do I reach for?
- Worked scenario: a secure three-tier setup
- “I can’t connect”: a troubleshooting checklist
- Common mistakes
- Wrapping up
Terminologies To Note
- The internet gateway is the VPC’s door to the internet.
- Route tables decide where traffic goes. They make a subnet public or private, and they never filter anything.
- Network ACLs are stateless allow-and-deny filters at the subnet boundary (i.e. NACL’s are stateless, the allow or deny traffic, & filters traffic at the subnet level).
- Security groups are stateful, allow-only filters at the instance.
- Traffic gets through only if a route exists, the NACL lets it in, the security group lets it in, and the reply can get back out.
The big picture: one VPC, four building blocks
A VPC (Virtual Private Cloud) is your own isolated network inside AWS. Think of it as a building. Inside it you divide the address space into subnets, which are the floors, and your servers (EC2 instances) live on those floors.
Every packet (Traffic or internet requests) moving through that building has to get past three questions, and each of our discussed components answers one of them:
- Can this traffic get here at all? The internet gateway and route tables decide whether a path exists.
- Is it allowed onto this floor? The network ACL checks traffic at the subnet boundary.
- Is it allowed into this specific room? The security group checks traffic at the Ec2 instance.
A fourth question follows: does the reply get back out? That’s where most of the confusion starts, and we’ll come back to it.
Hold on to the difference in Figure 1. Almost everything else in this article builds on it.
Internet gateway: the front door
Picture the building’s front door, opening onto the street. That’s the internet gateway (IGW): a managed, highly available component that you attach to a VPC. It is the only door between your VPC and the public internet. AWS scales it for you, so there is nothing to size or patch, and there is no charge for the gateway itself (normal data transfer charges still apply).
The IGW does two jobs. It gives the VPC a path to the internet, and it performs one-to-one network address translation for instances that have a public IPv4 address. Your instance only knows its private address, such as 10.0.1.25. The IGW swaps that for the public address (say 203.0.113.25) on the way out and swaps it back on the way in.
Attaching an IGW does not make anything public by itself. An instance can talk to the internet only when three things are true, shown in Figure 2.
Miss any one of them and the connection fails silently, which is why this topic generates so many support tickets.
For IPv6 there is a variant called an egress-only internet gateway. IPv6 addresses are globally unique, so there is no translation step. The egress-only gateway allows outbound connections and blocks connections started from the internet.
Use it for: load balancers, web servers, bastion hosts and NAT gateways in public subnets. Databases and internal services should never need a route to it.
Route tables: the building directory
If the IGW is the front door, the route table is the directory and signage on each floor. It tells traffic which way to go, and it never checks anyone’s ID.
A route table is a set of rules, called routes, that decides where traffic leaving a subnet is sent next. Each route has a destination (an address range) and a target (where to send matching traffic). Every subnet uses exactly one route table: the one you associate with it, or the VPC’s main route table if you don’t.
Three rules explain nearly everything:
- The local route always exists. It lets every resource (Private and public vm’s… etc.) in the VPC reach every other one, so traffic for the VPC’s own address range stays inside.
- The most specific route wins. If a destination matches more than one route, AWS uses the one with the longest prefix, as Figure 3 shows.
- A route table never filters. It chooses a path. It does not allow or deny anything.
This is also what makes a subnet public or private. A public subnet is simply one whose route table has a route to an internet gateway. A private subnet has no such route. The subnet’s name doesn’t matter, and neither does where it sits in the console. The route table decides.
Route tables can point at more than the IGW: NAT gateways, VPC peering connections, transit gateways, VPC endpoints and virtual private gateways. I covered the NAT gateway case, along with bastion hosts for admin access, in my earlier article.
A good habit: keep the main route table private, with only the local route, and create a separate custom route table with the IGW route for your public subnets. A new subnet is then private by default, and nobody makes something public by accident.
From routing to filtering: two checkpoints
So far nothing has been blocked. The IGW and route tables only decide whether a path exists. The two filters come next, and they sit at different levels:
- The network ACL guards the boundary of a subnet.
- The security group guards a single instance’s network interface.
Traffic entering a subnet from outside meets the NACL first, then the security group. Traffic leaving does the reverse. Figure 4 shows where each one sits.
One nuance catches people out. A NACL only sees traffic that crosses the subnet boundary. Two instances in the same subnet talk to each other without the NACL being involved, although security groups still apply.
Network ACLs: the gate at each floor
Imagine a security gate at the entrance to each floor. It checks everyone coming in and going out against a numbered list, and it has no memory: every pass through the gate is checked from scratch.
A network ACL (NACL) is a firewall at the subnet boundary. Its key properties:
- Subnet level. Each subnet is associated with exactly one NACL, and one NACL can cover many subnets.
- Stateless. It doesn’t remember connections. Inbound and outbound traffic are evaluated separately, so return traffic needs its own explicit rule.
- Allow and deny. A rule can ALLOW or DENY traffic.
- Numbered rules. Rules are evaluated from the lowest number up, and the first match wins. Inbound and outbound rules are separate lists, and every NACL ends with a rule, shown as *, that denies anything unmatched.
Leave gaps between rule numbers (100, 110, 120) so you can slot in new rules later.
The defaults matter. The default NACL in a default VPC allows all traffic in and out, so it does nothing until you change it. A new custom NACL does the opposite: it denies everything until you add rules.
Get Otti Chinedu Emmanuel’s stories in your inbox
Join Medium for free to get updates from this writer.
For private VMs, a NACL adds a coarse guardrail around the whole tier, for example a private-subnet NACL that only accepts traffic from the web subnet’s address range. When does a NACL really earn its place? Mostly when you need an explicit deny, such as blocking an abusive IP range, or when a compliance rule asks for a subnet-wide control on top of security groups.
Security groups: the guard at every office door
Now the guard at each office door. This guard keeps a list of who may enter, remembers who arrived with whom, and lets visitors leave without asking again.
A security group is a virtual firewall attached to an instance’s network interface. Its key properties:
- Instance level. One instance can have several security groups, and their rules combine.
- Stateful. If a request is allowed in, the reply is allowed out automatically, and the other way round.
- Allow rules only. There are no deny rules. Anything not explicitly allowed is blocked.
- Safe defaults. A new security group has no inbound rules, so it blocks all inbound traffic, and it allows all outbound traffic.
- Groups can reference groups. A rule’s source can be another security group instead of an IP range.
That last point is the secret of good tiered design. Instead of “allow port 5432 from 10.0.2.0/24”, you say “allow port 5432 from SG-app”. Any instance wearing the SG-app group is allowed, whatever its IP address, and nothing else is. Scale the app tier up or down and the rule keeps working.
For private VMs this is the main control. A database in a private subnet has no public path anyway, and its security group makes sure that only the app tier can talk to it.
Stateful vs. stateless: the difference behind most headaches
This is the difference behind most “it should work!” moments.
A connection has two halves: a request and a reply. A security group is stateful, so it tracks the connection and lets the reply through without a rule. A NACL is stateless, so it treats the reply as brand-new traffic and checks it against the outbound rules.
Here is the catch. A client doesn’t send its request from port 443. It picks a random high-numbered port, called an ephemeral port, and the server’s reply goes back to that port. So behind a NACL you need an inbound rule for TCP 443 and an outbound rule that allows replies to the client’s ephemeral ports. The exact range depends on the client’s operating system, which is why many guides allow 1024–65535 to cover every client.
Forget the outbound rule and the symptom is maddening: the request arrives, the server answers, and the client waits until it times out.
How they work together: the journey of a packet
Now let’s follow one request all the way through. A visitor opens your shop’s website, and the web server needs to fetch data from a private database. Figure 6 shows every checkpoint, in order.
Three things to take from the journey:
- Public and private differ only in the route. The same checks apply to both. A private tier just has no path to the IGW.
- Every hop is checked twice: at the subnet by the NACL, and at the instance by the security group.
- The reply has to make it back. The security group handles that automatically. The NACL needs its outbound rule, and the public subnet’s route to the IGW has to carry the reply out.
This is a conceptual model rather than a literal packet trace. AWS doesn’t expose these checks as separate hops you can watch, but reasoning in this order will rarely steer you wrong.
Side by side: the core differences
Here is everything so far on one page.
Routes decide where. NACLs and security groups decide whether.
Which one do I reach for?
- Internet gateway: whenever resources in your VPC need to talk to the internet directly. Attach one per VPC and keep it away from workloads that should stay private.
- Route tables: always. Every subnet has one. Use them to decide public vs. private and to send traffic to NAT gateways, peering connections, transit gateways or VPC endpoints.
- Security groups: always, as your main control. Put one on every instance and use security-group-to-security-group rules between tiers.
- NACLs: when you need an explicit deny or a coarse subnet-level guardrail. If you don’t, leave the defaults alone.
- Together: use layers, an approach called defense in depth. Security groups are the precise control and NACLs are the broad backstop. If one is misconfigured, the other still protects you.
Worked scenario: a secure three-tier setup
Let’s build a classic three-tier setup: a public web tier, a private app tier and a private database tier. Each tier gets its own subnet, its own NACL and its own security group. Only the web tier’s route table has a route to the internet gateway.
Start with the security groups. Table 2 lists the rules.
Notice there are no IP ranges between the tiers. Each rule trusts a security group, so the rules survive scaling. Security groups are stateful, so none of these rules needs a matching rule for replies. If you want to be strict, remove the default allow-all outbound rule and keep only the outbound rules shown.
As a taste of the command line, here is the database rule from the table, created with the AWS CLI:
aws ec2 authorize-security-group-ingress \
--group-id sg-0a1b2c3d4e5f60001 \
--protocol tcp --port 5432 \
--source-group sg-0a1b2c3d4e5f60002Here sg-0a1b2c3d4e5f60001 is SG-db and sg-0a1b2c3d4e5f60002 is SG-app. The IDs are placeholders.
The NACLs repeat the story at the subnet level. Because they are stateless, every conversation needs rules in both directions: the service port one way, and the ephemeral ports back (Table 3).
If the web servers also need to fetch updates or call external APIs, add an outbound HTTPS rule plus the matching ephemeral-port rule for the replies. Servers in private subnets can get outbound access through a NAT gateway instead.
Walk through the result once. The internet can reach only the web tier’s port 443. The web tier can reach only the app tier’s port 8080. The app tier can reach only the database’s port 5432. Nobody else can reach the database, and the internet can’t reach it at all.
“I can’t connect”: a troubleshooting checklist
When a connection fails, resist the urge to poke at the security group first. Work through the path in order, as Figure 8 shows.
In practice, the usual culprits are a missing security group rule and a NACL missing its ephemeral-port rule. Checking in path order stops you from guessing. If you’re still stuck, VPC Reachability Analyzer traces the path between two resources and tells you which component is in the way.
Common mistakes
- Attaching an IGW and stopping there. With no route and no public IP, there is no internet access.
- Adding the IGW route to the main route table. Every new subnet then becomes public by default. Use a custom route table for public subnets.
- Forgetting ephemeral ports in a NACL. The request gets in and the reply is dropped.
- Getting NACL rule order wrong. The first match wins, so a DENY with a lower number beats a later ALLOW. And remember that a new custom NACL denies everything until you add rules.
- Duplicating every rule in both NACLs and security groups. It doubles the work and the debugging. Use security groups for precision and keep NACLs simple.
- Opening SSH or RDP to 0.0.0.0/0. Ports 22 and 3389 should accept connections only from known IP ranges or, better, through a bastion host or Session Manager.
- Using IP ranges between tiers instead of security group references. The rules break when you scale or change addresses.
- Expecting a security group to deny. Security groups have no deny rules. To block an IP range, use a NACL.
Wrapping up
Think of the building one last time. The internet gateway is the front door. Route tables are the directory that points the way. NACLs are the gates at each floor, strict and forgetful. Security groups are the guards at each office door, careful and with a good memory.
A secure AWS network doesn’t depend on any one of them. It’s the layers working together: a path that exists only where you want it, a filter at the subnet and a filter at the instance.
Key takeaways
- Route tables decide where traffic goes. NACLs and security groups decide whether it is allowed. The IGW is the door to the internet.
- A subnet is public because its route table points at an IGW, not because of its name.
- NACLs are stateless and subnet-level, with ordered ALLOW and DENY rules. Security groups are stateful and instance-level, with allow rules only.
- NACLs need rules for return traffic (ephemeral ports). Security groups don’t.
- Make security groups your main control, with security-group-to-security-group rules between tiers, and use NACLs as a coarse backstop.
- When troubleshooting, follow the packet: IGW and public IP, route, NACL, security group, then the OS.
Keep reading: private servers still need a way to be updated and managed. I cover exactly that in my earlier article, One Door In (Bastion Host), One Window Out (NATGateway).







