AWS NACL vs Security Group: The Practical Guide (With Console Walkthroughs)
Security Groups are stateful bouncers on the instance/ENI. NACLs are stateless speed bumps at the subnet edge. That is the core idea. The rest is details, and you are here for the details.
TL;DR
Level
Instance/ENI
Subnet
Stateful or Stateless
Stateful
Stateless
Rule Types
Allow only
Allow and Deny
Applies To
ENIs/Instances
Entire Subnet
Rule Evaluation
All rules are evaluated as a set
Rules are processed by number (lowest first)
Defaults
Inbound denied, outbound allowed
Default NACL allows all; you can change it
Typical Use
App-level firewalling
Broad subnet filtering and quarantine
Logging
VPC Flow Logs (via ENI or Subnet)
VPC Flow Logs (via Subnet)
Console Path
EC2 -> Security Groups
VPC -> Network ACLs
If you only remember one thing:
SG = stateful allow-lists on ENIs;
NACL = stateless allow+deny lists on subnets with ordered rules.
Why you should care
Reality check:
SGs do not support deny.
NACLs do.
SGs track connections;
NACLs do not.
NACL rule order matters
SG rule order does not.
Real world: Subnet-wide quarantine, hot IP blocks, blast-radius control, and keeping your SGs clean all hinge on knowing which lever to pull.
Performance note: NACLs are checked per packet; SGs are connection-aware. If you are pushing very high packet rates, the difference shows up in behaviour and troubleshooting.
See it yourself: AWS Console Walkthroughs
Security Groups
Open AWS Console -> EC2 -> Security Groups.
Click a group. Inspect Inbound rules and Outbound rules tabs.
Click the References/Attached resources tab to see ENIs/instances using this SG.
Try to add a Deny. You cannot. SGs are allow-only.
Housekeeping: Filter for groups with zero attached network interfaces. Those are candidates for cleanup.
Network ACLs
Open AWS Console -> VPC -> Network ACLs.
Pick a NACL. Inspect Inbound rules and Outbound rules. Note the rule numbers and the implicit deny at the end.
Check the Subnet associations tab to see which subnets inherit these rules.
Create a Deny rule for a test source IP and give it a small rule number (for example 100) so it wins.
Housekeeping: Filter for NACLs with zero subnet associations. Those are usually safe to archive or delete after review.
Pro tip: Make one small change at a time, then test. NACL mistakes are easy to make and hard to debug if you pile on changes. (been here)
Hands-on mini lab (15-30 min)
Goal: Experience stateful vs stateless behavior and allow vs deny in a safe sandbox.
Setup
One VPC with two public subnets: web-subnet and admin-subnet.
An EC2 instance in web-subnet (call it web1).
An EC2 instance in admin-subnet (call it admin1).
An Internet Gateway and route tables so both are reachable.
Security Groups
SG-web: inbound allow TCP 80 from 0.0.0.0/0, outbound allow all.
SG-admin: inbound allow TCP 22 only from your public IP, outbound allow all.
NACLs
NACL-web: allow inbound 80 from 0.0.0.0/0, allow ephemeral response ports outbound. No explicit deny.
NACL-admin: add a deny inbound rule for your home IP block as a test (for example, rule 100 deny 203.0.113.0/24), then allow the rest.
Tests
From your laptop, curl http://web1-public-ip. Should work.
From your laptop, ssh admin1. If your IP matches the deny in NACL-admin, you should be blocked regardless of SG.
From admin1, curl http://web1-private-ip. This should work if SGs allow it. If you add a NACL deny between the subnets, it will fail even if SGs allow.
Last updated