Security Groups & Network ACLs
Key concepts
Security groups are stateful
NACLs are stateless
Security groups at instance level
NACLs at subnet level
Rule evaluation order
Overview
AWS provides two primary mechanisms for controlling network traffic in a VPC: Security Groups and Network Access Control Lists (NACLs). Understanding the differences between them - particularly stateful vs stateless behavior - is critical for both the SAA-C03 exam and building secure architectures.
Security Groups operate at the instance level and are stateful, meaning return traffic is automatically allowed. NACLs operate at the subnet level and are stateless, requiring explicit rules for both inbound and outbound traffic. Together, they implement a defense-in-depth strategy.
The Key Difference
Security Groups = Stateful (return traffic automatically allowed) NACLs = Stateless (must explicitly allow return traffic)
This fundamental difference affects how you write rules and is frequently tested on the exam.
If a question mentions 'blocking a specific IP address,' think NACLs - Security Groups only have Allow rules. If it mentions 'instance-level' control or 'stateful,' think Security Groups.
Architecture Diagram

Key Concepts
Security Groups
Security Groups
Security Groups act as virtual firewalls for EC2 instances and other resources.
Key Characteristics:
- Stateful: Return traffic automatically allowed
- Instance-level: Applied to ENIs (Elastic Network Interfaces)
- Allow rules only: Cannot create deny rules
- All rules evaluated: No rule ordering
- Default behavior: Deny all inbound, allow all outbound
What Can Use Security Groups:
- EC2 instances
- RDS instances
- Lambda (VPC)
- ELB/ALB/NLB
- ElastiCache
- EFS mount targets
- VPC endpoints
Network ACLs (NACLs)
Network ACLs
Network ACLs are stateless firewalls that control traffic at the subnet boundary.
Key Characteristics:
- Stateless: Must allow both inbound AND outbound explicitly
- Subnet-level: Affects all instances in the subnet
- Allow AND Deny rules: Can explicitly block traffic
- Rules processed in order: Lowest number first, first match wins
- Default behavior: Allow all (default NACL), Deny all (custom NACL)
NACL Rule Components:
- Rule number (1-32766)
- Protocol (TCP, UDP, ICMP, all)
- Port range
- Source/Destination CIDR
- Allow or Deny
Stateful vs Stateless Explained
Stateful Behavior
Stateful (Security Groups):
When you allow inbound traffic on port 443:
- Inbound: HTTPS requests come in ✓
- Outbound: Responses go out automatically ✓ (no outbound rule needed)
The security group "remembers" the connection and allows the response.
Inbound Rule: Allow HTTPS (443) from 0.0.0.0/0
Outbound Rule: Not needed for responses to inbound requests
Stateless Behavior
Stateless (NACLs):
When you allow inbound traffic on port 443:
- Inbound: HTTPS requests come in ✓
- Outbound: Responses BLOCKED ✗ (unless explicitly allowed)
You MUST create an outbound rule for the response traffic (ephemeral ports).
Inbound Rule: Allow HTTPS (443) from 0.0.0.0/0
Outbound Rule: Allow TCP 1024-65535 to 0.0.0.0/0 (ephemeral ports for responses)
Comparison Table
Security Groups vs NACLs
| Feature | Security Groups | NACLs |
|---|---|---|
| Level | Instance (ENI) | Subnet |
| State | Stateful | Stateless |
| Rule Types | Allow only | Allow AND Deny |
| Rule Processing | All rules evaluated | Rules processed in order |
| Default Inbound | Deny all | Allow all (default NACL) |
| Default Outbound | Allow all | Allow all (default NACL) |
| Applies To | Specific resources | All resources in subnet |
| Return Traffic | Automatic | Must be explicitly allowed |
| Use Case | Primary access control | Subnet-level backup/block lists |
How It Works
Security Group Rules
INBOUND RULES:
┌─────────┬──────────┬───────────┬─────────────────────┐
│ Type │ Protocol │ Port │ Source │
├─────────┼──────────┼───────────┼─────────────────────┤
│ HTTPS │ TCP │ 443 │ 0.0.0.0/0 │
│ SSH │ TCP │ 22 │ 10.0.0.0/8 │
│ Custom │ TCP │ 3306 │ sg-app-servers │
│ All │ All │ All │ sg-self (self-ref) │
└─────────┴──────────┴───────────┴─────────────────────┘
OUTBOUND RULES:
┌─────────┬──────────┬───────────┬─────────────────────┐
│ Type │ Protocol │ Port │ Destination │
├─────────┼──────────┼───────────┼─────────────────────┤
│ All │ All │ All │ 0.0.0.0/0 │
└─────────┴──────────┴───────────┴─────────────────────┘
Note: Outbound "allow all" is the default. Responses to inbound
requests work automatically due to stateful behavior.NACL Rules
INBOUND RULES:
┌────────┬─────────┬──────────┬───────────┬───────────────┬────────┐
│ Rule # │ Type │ Protocol │ Port │ Source │ Action │
├────────┼─────────┼──────────┼───────────┼───────────────┼────────┤
│ 100 │ HTTPS │ TCP │ 443 │ 0.0.0.0/0 │ ALLOW │
│ 110 │ HTTP │ TCP │ 80 │ 0.0.0.0/0 │ ALLOW │
│ 120 │ SSH │ TCP │ 22 │ 10.0.0.0/8 │ ALLOW │
│ 130 │ Custom │ TCP │ 1024-65535│ 0.0.0.0/0 │ ALLOW │
│ * │ All │ All │ All │ 0.0.0.0/0 │ DENY │
└────────┴─────────┴──────────┴───────────┴───────────────┴────────┘
OUTBOUND RULES:
┌────────┬─────────┬──────────┬───────────┬───────────────┬────────┐
│ Rule # │ Type │ Protocol │ Port │ Destination │ Action │
├────────┼─────────┼──────────┼───────────┼───────────────┼────────┤
│ 100 │ HTTPS │ TCP │ 443 │ 0.0.0.0/0 │ ALLOW │
│ 110 │ HTTP │ TCP │ 80 │ 0.0.0.0/0 │ ALLOW │
│ 120 │ Custom │ TCP │ 1024-65535│ 0.0.0.0/0 │ ALLOW │
│ * │ All │ All │ All │ 0.0.0.0/0 │ DENY │
└────────┴─────────┴──────────┴───────────┴───────────────┴────────┘
Note: Rule 130 inbound allows ephemeral ports for responses.
Rule 120 outbound allows ephemeral ports for responses to outbound requests.
Rule * is the default deny (always last, cannot be deleted).Ephemeral Ports
Ephemeral Ports
Ephemeral ports (1024-65535) are temporary ports used for the return traffic of a connection.
When a client connects to your server on port 443:
- Client uses ephemeral port (e.g., 52436) as source
- Server responds to that ephemeral port
- NACL must allow outbound traffic to ephemeral ports for responses
Different OS ranges:
- Linux: 32768-60999
- Windows: 49152-65535
- Safe range: 1024-65535 (covers all)
For NACLs, always include ephemeral port rules!
Creating Security Groups (CLI)
# Create security group
aws ec2 create-security-group \
--group-name WebServer-SG \
--description "Security group for web servers" \
--vpc-id vpc-xxx
# Add inbound rule for HTTPS
aws ec2 authorize-security-group-ingress \
--group-id sg-xxx \
--protocol tcp \
--port 443 \
--cidr 0.0.0.0/0
# Add inbound rule referencing another security group
aws ec2 authorize-security-group-ingress \
--group-id sg-database \
--protocol tcp \
--port 3306 \
--source-group sg-app-servers
# Revoke a rule
aws ec2 revoke-security-group-ingress \
--group-id sg-xxx \
--protocol tcp \
--port 22 \
--cidr 0.0.0.0/0Creating NACLs (CLI)
# Create NACL
aws ec2 create-network-acl \
--vpc-id vpc-xxx
# Add inbound allow rule
aws ec2 create-network-acl-entry \
--network-acl-id acl-xxx \
--rule-number 100 \
--protocol tcp \
--port-range From=443,To=443 \
--cidr-block 0.0.0.0/0 \
--rule-action allow \
--ingress
# Add inbound deny rule (block specific IP)
aws ec2 create-network-acl-entry \
--network-acl-id acl-xxx \
--rule-number 50 \
--protocol tcp \
--port-range From=0,To=65535 \
--cidr-block 1.2.3.4/32 \
--rule-action deny \
--ingress
# Add outbound rule for ephemeral ports
aws ec2 create-network-acl-entry \
--network-acl-id acl-xxx \
--rule-number 100 \
--protocol tcp \
--port-range From=1024,To=65535 \
--cidr-block 0.0.0.0/0 \
--rule-action allow \
--egress
# Associate NACL with subnet
aws ec2 replace-network-acl-association \
--association-id aclassoc-xxx \
--network-acl-id acl-xxxUse Cases
Use Case 1: Web Application Security
Scenario: Three-tier web application with ALB, app servers, and database.

Security Groups:
- ALB-SG: Inbound 443 from 0.0.0.0/0
- App-SG: Inbound 8080 from ALB-SG only
- DB-SG: Inbound 3306 from App-SG only
NACLs:
- Default allow (rely on security groups for primary control)
Use Case 2: Block Known Malicious IPs
Scenario: Need to block traffic from known bad actors.

Solution: Use NACLs with deny rules
- Rule 10: DENY all traffic from 1.2.3.4/32
- Rule 20: DENY all traffic from 5.6.7.0/24
- Rule 100+: Allow legitimate traffic
Security groups cannot do this (no deny rules).
Use Case 3: Restrict SSH Access
Scenario: SSH should only be accessible from corporate network.
Security Group:
- Inbound: SSH (22) from 10.0.0.0/8 (corporate CIDR)
NACL (additional layer):
- Rule 90: DENY SSH (22) from 0.0.0.0/0
- Rule 100: ALLOW SSH (22) from 10.0.0.0/8
Use Case 4: Self-Referencing Security Group
Scenario: Allow all instances in an Auto Scaling group to communicate with each other.
Security Group:
Inbound: All traffic from sg-self (reference same SG)
This allows any instance with this SG to talk to any other instance with the same SG.
Best Practices
Security Group Best Practices
- Start with Least Privilege - Begin with no inbound rules, add as needed
- Use Security Group References - Reference other SGs instead of CIDR when possible
- One Purpose per SG - Create separate SGs for web, app, database tiers
- Avoid 0.0.0.0/0 - Be specific about sources when possible
- Name and Tag Descriptively - Easy to understand purpose
- Review Regularly - Remove unused rules
- Use for Primary Control - More flexible than NACLs
NACL Best Practices
- Use as Secondary Control - Primary control should be Security Groups
- Leave Gaps in Rule Numbers - Use 100, 110, 120... to allow insertions
- Always Include Ephemeral Ports - Required for stateless responses
- Lower Numbers for Denies - Process deny rules before allow rules
- Document Rule Purpose - NACLs don't have descriptions
- Use for Subnet-Wide Blocks - Blocking bad IPs, compliance requirements
- Default NACL is Permissive - Create custom NACLs for production
Common Exam Scenarios
Exam Scenarios
| Scenario | Solution | Why |
|---|---|---|
| Block specific IP from accessing EC2 | NACL deny rule | Security Groups only have allow rules |
| Allow app servers to access RDS | Security Group referencing app SG | Instance-level, stateful, SG-to-SG reference |
| Allow HTTPS but connection times out | Check NACL outbound ephemeral ports | Stateless - needs explicit outbound rule |
| Allow instances in same tier to communicate | Self-referencing Security Group | SG can reference itself |
| Web traffic allowed but SSH blocked for subnet | NACL with allow HTTP/HTTPS, deny SSH | Subnet-level control with deny capability |
| Need to allow return traffic | Security Group: automatic; NACL: explicit rule | Stateful vs stateless behavior |
| Multiple security groups on instance | All rules combined (union) | All SG rules are evaluated together |
| Rule order matters | NACL only (lowest number first) | SGs evaluate all rules; NACLs are ordered |
Common Pitfalls
Pitfall 1: Forgetting Ephemeral Ports in NACLs
Mistake: Adding NACL inbound rule for HTTPS but no outbound rule for responses.
Symptom:
- Connections time out
- Requests seem to reach server but no response
Why it happens:
- NACLs are stateless
- Responses need explicit outbound rule
- Ephemeral ports (1024-65535) must be allowed
Correct Approach:
- Always add outbound rule for ephemeral ports
- Or use default NACL (allows all)
Pitfall 2: Using NACL When Security Group Would Work
Mistake: Creating complex NACL rules when Security Groups would be simpler.
Why it's problematic:
- NACLs are harder to manage (stateless)
- Affects entire subnet, not specific resources
- Rule ordering can be confusing
Correct Approach:
- Use Security Groups as primary control
- Use NACLs only for subnet-wide policies or deny rules
- Keep NACLs simple
Pitfall 3: Expecting NACL Rule Order Like SGs
Mistake: Assuming all NACL rules are evaluated like Security Groups.
Why it causes issues:
- NACL rules processed in order (lowest first)
- First match wins, stops processing
- Rule 100 ALLOW then Rule 200 DENY = ALLOWED
Correct Approach:
- Put DENY rules with lower numbers than ALLOW
- Example: Rule 50 DENY bad IP, Rule 100 ALLOW good traffic
Pitfall 4: Using 0.0.0.0/0 Everywhere
Mistake: Allowing all sources (0.0.0.0/0) for convenience.
Why it's dangerous:
- Exposes resources to entire internet
- Increases attack surface
- Violates least privilege
Correct Approach:
- Be specific about source CIDRs
- Use Security Group references when possible
- Only use 0.0.0.0/0 for public-facing services (ALB)
Test Your Knowledge
What is the key difference between Security Groups and NACLs?
A web server's Security Group allows inbound HTTPS (443). The NACL allows inbound 443 but users cannot connect. What is likely missing?
You need to block a specific IP address from accessing any resource in a subnet. Which should you use?
An EC2 instance has two Security Groups attached. SG1 allows SSH, SG2 does not mention SSH. Is SSH allowed?
Related Services
Quick Reference
Security Group Limits
Security Group Limits
| Limit | Default Value |
|---|---|
| Security groups per VPC | 2,500 |
| Rules per security group | 60 inbound + 60 outbound |
| Security groups per ENI | 5 (can increase to 16) |
| Rules evaluated per ENI | 1,000 |
NACL Limits
NACL Limits
| Limit | Default Value |
|---|---|
| NACLs per VPC | 200 |
| Rules per NACL | 20 inbound + 20 outbound |
| NACLs per subnet | 1 (one-to-one) |
| Subnets per NACL | Unlimited |
Common Port Numbers
HTTP: 80
HTTPS: 443
SSH: 22
RDP: 3389
MySQL: 3306
PostgreSQL: 5432
SQL Server: 1433
Redis: 6379
Memcached: 11211
NFS (EFS): 2049
SMTP: 25 (or 587 for TLS)
DNS: 53 (TCP and UDP)
Ephemeral: 1024-65535 (safe range for all OS)CLI Commands Reference
# List security groups
aws ec2 describe-security-groups --filters "Name=vpc-id,Values=vpc-xxx"
# List security group rules
aws ec2 describe-security-group-rules --filters "Name=group-id,Values=sg-xxx"
# List NACLs
aws ec2 describe-network-acls --filters "Name=vpc-id,Values=vpc-xxx"
# Get NACL entries
aws ec2 describe-network-acls --network-acl-ids acl-xxx
# Delete NACL entry
aws ec2 delete-network-acl-entry \
--network-acl-id acl-xxx \
--rule-number 100 \
--ingress