Security Groups vs Network ACLs
Introduction¶
AWS gives you two packet filters in a VPC. They operate at different layers and one critical difference — stateful vs stateless — explains most "I opened the port but it still doesn't work" tickets.
The comparison¶
| Security Group | Network ACL | |
|---|---|---|
| Attached to | ENI (instance, LB, RDS, etc.) | subnet |
| Stateful? | Yes — return traffic auto-allowed | No — must allow both directions |
| Rules | allow only | allow and deny |
| Evaluation | all rules, any match = allow | numbered, first match wins |
| Default | deny all inbound, allow all outbound | default NACL allows all; custom denies all |
| Scope | one resource (but reusable) | every ENI in the subnet |
Stateful (security group)¶
If you allow inbound TCP 443, the response traffic (from the instance back to the client, on a high source port) is automatically allowed — the SG tracks the connection. You only write the rule for the direction you initiate.
Inbound: TCP 443 from 0.0.0.0/0
Outbound: (default) all
That's a complete rule set for a web server. Reference other SGs as the source
(allow 5432 from sg-app) instead of IP ranges — cleaner and it follows
instances as they scale.
Stateless (NACL)¶
A NACL evaluates each packet independently with no memory of the connection. If you allow inbound 443, the response going back out is checked against the outbound rules — and clients connect from ephemeral ports, so you must allow outbound to the ephemeral range:
Inbound 100 TCP 443 0.0.0.0/0 ALLOW
Inbound * ALL 0.0.0.0/0 DENY
Outbound 100 TCP 1024-65535 0.0.0.0/0 ALLOW <- return traffic!
Outbound * ALL 0.0.0.0/0 DENY
Forget the outbound ephemeral rule and every connection hangs — the SYN-ACK gets in but the rest of the handshake's return packets are dropped.
Ephemeral port ranges vary by client OS (Linux 32768–60999, Windows 49152–65535, NLB 1024–65535) — allow 1024–65535 to be safe.
When to use NACLs¶
Security groups handle the vast majority of needs. Reach for NACLs when:
- You need an explicit deny (block a specific malicious IP range across a whole subnet) — SGs can't deny.
- Defence in depth — a coarse subnet-level backstop so a misconfigured SG doesn't fully expose a private subnet.
- Isolating subnets from each other regardless of SG config.
Don't use NACLs for fine-grained per-app rules — the stateless return-traffic management makes them error-prone.
Rule ordering (NACL)¶
Rules are processed lowest number first; the first match wins and stops
evaluation. Leave gaps (100, 200, 300) so you can insert later. The final *
rule is an implicit DENY you can't remove.
Verification and troubleshooting¶
aws ec2 describe-security-groups --group-ids sg-0abc
aws ec2 describe-network-acls --filters Name=association.subnet-id,Values=subnet-0abc
# VPC Flow Logs show ACCEPT/REJECT and which layer
aws ec2 create-flow-logs --resource-type Subnet --resource-ids subnet-0abc ...
- SG rule added, still can't connect — check the other end's SG (the
client/LB), the NACL on both subnets, the route table (is there a path?), and
whether the instance OS firewall (
ufw/firewalld) is also filtering. - Connection hangs (not refused) — asymmetric filtering. Classic NACL
missing-ephemeral-outbound. Flow Logs show
REJECTon the return packets. - Works within the subnet, fails across subnets — NACL between them, or a missing route.
- RDS/ElastiCache unreachable from the app — the DB's SG must allow inbound from the app's SG on the DB port. Referencing the app SG is the clean way.
- Intermittent failures under load — NACL connection tracking has limits for some protocols, or you're hitting the SG rule count / referenced-SG limits.
- Flow Logs show ACCEPT but the app still can't talk — filtering passed;
it's the OS firewall, the app not listening on
0.0.0.0, or DNS. - Removed a NACL deny rule, still blocked — NACL changes apply to new connections; existing tracked-by-SG connections and caches may persist briefly.
Related tools and reading¶
- On-site: Port Checker, Subnet Calculator.
- Related posts: VPC subnets and route tables, AWS IAM policy evaluation logic.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in Cloud & AWS
AWS WAF: The Rules That Matter
AWS WAF filters HTTP requests at the ALB/CloudFront/API Gateway edge. Here's a sensible baseline Web ACL, how to tune with count mode, and the false-positive traps.
October 24, 2026An S3 Static Site Behind CloudFront
The right way to host a static site on AWS: a private S3 bucket, CloudFront with OAC in front, HTTPS via ACM, and SPA fallback routing. Plus the 403s that trip everyone.
October 24, 2026CloudWatch Logs and Metric Filters
CloudWatch Logs is cheap to write and expensive to forget about. Here's retention, turning error patterns into metrics and alarms, and Logs Insights for ad-hoc queries.
October 24, 2026