A patient opens your portal over HTTPS and the page never loads. Somewhere between the internet and the web server, a firewall said no. In a VPC there are two candidates, and they behave so differently that the fix for one does nothing for the other. Knowing which one dropped the packet is most of the job.
Two firewalls in two places
A security group “controls the traffic that is allowed to reach and leave the resources that it is associated with”, such as an EC2 instance. A network ACL “allows or denies specific inbound or outbound traffic at the subnet level”. The VPC guide’s comparison table sums up the rest:
| Security group | Network ACL | |
|---|---|---|
| Level of operation | Instance level | Subnet level |
| Rule type | Allow rules only | Allow and deny rules |
| Rule evaluation | All rules, before deciding | In ascending order until a match |
| Return traffic | Automatically allowed (stateful) | Must be explicitly allowed (stateless) |
Every subnet uses exactly one network ACL. “A subnet can be associated with only one network ACL at a time”, and a subnet you never associate uses the VPC’s default network ACL.
Follow one HTTPS request
A patient’s browser connects from its own address, and from a source port its device picked, to port 443 on the web server. Four checks stand in the way.
- Into the subnet: the network ACL’s inbound rules. One must allow TCP 443 from the patient.
- Into the instance: the security group’s inbound rules. One must allow TCP 443 from the patient, or from
0.0.0.0/0for a public site. - Out of the instance: nothing to check. Security groups are stateful. “Responses to allowed inbound traffic are allowed to leave the instance, regardless of the outbound rules.”
- Out of the subnet: the network ACL’s outbound rules. Network ACLs are stateless, so the reply is judged as new traffic. And the reply is not going to port 443. It goes to the patient’s source port.
Step 4 catches most people. That source port is an ephemeral port, and “The client that initiates the request chooses the ephemeral port range.” The VPC guide lists examples: many Linux kernels use 32768-61000, Windows Server 2008 and later use 49152-65535, and NAT gateways and Elastic Load Balancing use 1024-65535. A network ACL that only allows outbound 443 drops every reply. For public-facing servers the guide’s advice is that “you can open ephemeral ports 1024-65535”, and add deny rules for any malicious ports in that range, placed earlier in the table.
The same logic runs the other way when your instance is the client. Its replies come back into the subnet on its own ephemeral ports, so the inbound rules must allow them.
Rule numbers decide
Each network ACL rule has a number from 1 to 32766. The VPC guide: “We evaluate the rules in order, starting with the lowest numbered rule”. When traffic matches a rule, that rule applies and no further rules are evaluated. Every network ACL also ends with a rule numbered * that denies anything no other rule matched, and you can’t delete it.
So a deny rule is only as good as its number. Suppose rule 100 allows TCP 443 from 0.0.0.0/0 and you must block a hostile address range. A deny at rule 150 never fires, because rule 100 matched first. The deny has to come before the allow, at rule 90 for example. This is also why the guide recommends numbering in increments of 10 or 100: you leave room to insert rules later.
Blocking an address range is a network ACL job for a simple reason. For security groups, “You can specify allow rules, but not deny rules.”
Inside a subnet, only the security group counts
Network ACL rules are evaluated “when traffic enters and leaves the subnet, not as it is routed within a subnet”. Two instances in the same subnet never pass a network ACL between them. If an analytics server must not reach the database beside it, the database’s security group is the only thing that can stop it.
That is where the default security group bites. Its default inbound rule “Allows inbound traffic from all resources that are assigned to this security group”, and an instance launched without a chosen group gets it. Put a web server, a database and an analytics server on the default group, and each can reach the others on every port.
The fix is a group per tier, with rules that name a group instead of an address. When a rule’s source is a security group, it allows traffic from the instances associated with that group. The VPC guide’s own example gives the database tier a rule whose source “is the security group for the web servers”. It matches the web tier’s instances in the same VPC (or a peered one), by their private IP addresses, and nothing else in the subnet.
What the exam guides ask
The SAA-C03 guide covers this in Task 1.2 “Design secure workloads and applications”. The SOA-C03 guide has Skill 5.1.1 “Configure a VPC” and Skill 5.3.1 “Troubleshoot VPC configurations”. The SCS-C03 guide has Skill 3.3.1 “Design and troubleshoot appropriate network controls to permit or prevent network traffic as required”.
Scenario questions on the topic hand you a flow that fails, or an address range that must be blocked, and ask for the least-privilege fix. The wrong answers are predictable:
- A deny rule in a security group. There is no such rule.
- A return-traffic rule in a security group. Not needed: replies are allowed automatically.
- An outbound network ACL rule for the service port only. The reply goes to an ephemeral port.
- A deny rule numbered after the allow. For traffic the allow already matches, it is never reached.
- A network ACL rule between two instances in one subnet. That traffic never crosses a network ACL.
- The whole subnet’s CIDR as a source. It works, but trusts every address in it. Naming the tier’s security group is tighter.
When you troubleshoot for real, ask two questions in order. Did the subnet boundary allow the traffic both ways, including the reply to the ephemeral port? Did the destination’s security group allow it in?
Play it
In Guard Rails, Harbor Health runs a patient portal in portal-vpc. portal-web sits in a public subnet behind web-nacl. records-db, a MySQL server, and analytics-server share a private subnet. A developer launched all three on the default security group. Patients must reach the portal over HTTPS, only the web tier may query the database, nobody on the internet may open SSH, and a flagged botnet range must be blocked at the subnet boundary. You place security groups and edit rules in the Inspector, then send traffic and watch which layer stops each packet. Guard Rails is part of the paid Networking domain. The free first stage, Open for Business, is a Network build too and plays the same way.