Skip to main content

Security Groups vs NACL

Introduction​

AWS offers two levels of firewalls to protect your resources:

  • Security Groups: at the instance level
  • NACL (Network ACL): at the subnet level
┌─────────────────────────────────────────────────────────────┐
│ TWO LEVELS OF SECURITY │
├─────────────────────────────────────────────────────────────┤
│ │
│ INTERNET │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ SUBNET │ │
│ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ NACL (1st level) │ │ │
│ │ │ Filters ALL inbound/outbound traffic │ │ │
│ │ │ of the subnet │ │ │
│ │ └───────────────────┬────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ EC2 INSTANCE │ │ │
│ │ │ ┌──────────────────────────────────────────┐ │ │ │
│ │ │ │ SECURITY GROUP (2nd level) │ │ │ │
│ │ │ │ Filters traffic to/from instance │ │ │ │
│ │ │ └──────────────────────────────────────────┘ │ │ │
│ │ │ │ │ │ │
│ │ │ ┌─────▼─────┐ │ │ │
│ │ │ │ Application│ │ │ │
│ │ │ └───────────┘ │ │ │
│ │ └────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

Security Groups​

Characteristics​

┌─────────────────────────────────────────────────────────────┐
│ SECURITY GROUPS │
├─────────────────────────────────────────────────────────────┤
│ │
│ TYPE: Stateful firewall │
│ │
│ Stateful means: │
│ • If inbound traffic is allowed │
│ • Then the outbound response is automatically permitted │
│ • No need for explicit rule for the response │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Client ──── Request ────► Instance │ │
│ │ (allowed by inbound rule) │ │
│ │ │ │
│ │ Client ◄─── Response ──── Instance │ │
│ │ (automatically allowed - stateful) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ OPERATION │
│ • By default: ALL blocked inbound │
│ • By default: ALL allowed outbound │
│ • ALLOW rules only (no DENY) │
│ • Evaluated all together (no order) │
│ │
└─────────────────────────────────────────────────────────────┘

Anatomy of a SG Rule​

┌─────────────────────────────────────────────────────────────┐
│ SECURITY GROUP RULE │
├─────────────────────────────────────────────────────────────┤
│ │
│ Type : SSH │
│ Protocol : TCP │
│ Port Range : 22 │
│ Source : 203.0.113.0/24 │
│ Description: Admin access from office │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ MEANING: │ │
│ │ │ │
│ │ "Allow TCP traffic on port 22 (SSH) │ │
│ │ from IPs 203.0.113.0 to 203.0.113.255" │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ POSSIBLE SOURCES: │
│ • Single IP : 203.0.113.45/32 │
│ • CIDR Range : 203.0.113.0/24 │
│ • Another SG : sg-0123456789 │
│ • Prefix List : pl-xxx │
│ • Everywhere : 0.0.0.0/0 (⚠️ caution!) │
│ │
└─────────────────────────────────────────────────────────────┘

Security Groups Examples​

┌─────────────────────────────────────────────────────────────┐
│ SECURITY GROUP - WEB SERVER │
├─────────────────────────────────────────────────────────────┤
│ │
│ Name: sg-web-server │
│ │
│ INBOUND RULES │
│ ┌───────┬──────────┬───────┬─────────────────────────┐ │
│ │ Type │ Protocol │ Port │ Source │ │
│ ├───────┼──────────┼───────┼─────────────────────────┤ │
│ │ HTTP │ TCP │ 80 │ 0.0.0.0/0 │ │
│ │ HTTPS │ TCP │ 443 │ 0.0.0.0/0 │ │
│ │ SSH │ TCP │ 22 │ 10.0.0.0/16 (VPC only) │ │
│ └───────┴──────────┴───────┴─────────────────────────┘ │
│ │
│ OUTBOUND RULES │
│ ┌───────┬──────────┬───────┬─────────────────────────┐ │
│ │ Type │ Protocol │ Port │ Destination │ │
│ ├───────┼──────────┼───────┼─────────────────────────┤ │
│ │ All │ All │ All │ 0.0.0.0/0 │ │
│ └───────┴──────────┴───────┴─────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ SECURITY GROUP - DATABASE │
├─────────────────────────────────────────────────────────────┤
│ │
│ Name: sg-database │
│ │
│ INBOUND RULES │
│ ┌────────┬──────────┬───────┬────────────────────────┐ │
│ │ Type │ Protocol │ Port │ Source │ │
│ ├────────┼──────────┼───────┼────────────────────────┤ │
│ │ MySQL │ TCP │ 3306 │ sg-web-server ✓ │ │
│ │ MySQL │ TCP │ 3306 │ sg-app-server ✓ │ │
│ └────────┴──────────┴───────┴────────────────────────┘ │
│ │
│ ✓ Referencing another SG = best practice │
│ Only instances in this SG can access │
│ │
│ OUTBOUND RULES │
│ ┌───────┬──────────┬───────┬─────────────────────────┐ │
│ │ Type │ Protocol │ Port │ Destination │ │
│ ├───────┼──────────┼───────┼─────────────────────────┤ │
│ │ All │ All │ All │ 0.0.0.0/0 │ │
│ └───────┴──────────┴───────┴─────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

Network ACL (NACL)​

Characteristics​

┌─────────────────────────────────────────────────────────────┐
│ NETWORK ACL (NACL) │
├─────────────────────────────────────────────────────────────┤
│ │
│ TYPE: Stateless firewall │
│ │
│ Stateless means: │
│ • Each direction must be explicitly allowed │
│ • If inbound is allowed, outbound must also be configured │
│ • Separate rules for inbound AND outbound │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Client ──── Request ────► Subnet │ │
│ │ (needs inbound rule) │ │
│ │ │ │
│ │ Client ◄─── Response ──── Subnet │ │
│ │ (needs separate outbound rule!) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ OPERATION │
│ • By default: ALL allowed (default NACL) │
│ • ALLOW and DENY rules │
│ • Evaluated in ORDER of numbers (priority) │
│ • First matching rule = final decision │
│ • Applies to the WHOLE subnet │
│ │
└─────────────────────────────────────────────────────────────┘

Anatomy of a NACL Rule​

┌─────────────────────────────────────────────────────────────┐
│ NACL RULE │
├─────────────────────────────────────────────────────────────┤
│ │
│ Rule Number : 100 │
│ Type : HTTP (80) │
│ Protocol : TCP (6) │
│ Port Range : 80 │
│ Source : 0.0.0.0/0 │
│ Allow/Deny : ALLOW │
│ │
│ EVALUATION ORDER: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Rule 100: ALLOW HTTP from 0.0.0.0/0 │ │
│ │ Rule 200: DENY HTTP from 192.168.1.0/24 │ │
│ │ Rule * : DENY ALL │ │
│ │ │ │
│ │ HTTP packet from 192.168.1.50? │ │
│ │ → Evaluated by Rule 100 first │ │
│ │ → 192.168.1.50 matches 0.0.0.0/0 │ │
│ │ → ALLOW! (Rule 200 never reached) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ⚠️ Rule order is CRITICAL in NACL │
│ │
└─────────────────────────────────────────────────────────────┘

Ephemeral Ports​

┌─────────────────────────────────────────────────────────────┐
│ EPHEMERAL PORTS (Important for NACL) │
├─────────────────────────────────────────────────────────────┤
│ │
│ NACL Problem: How to allow responses? │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Client (port 54321) ──HTTP:80──► Server │ │
│ │ Client (port 54321) ◄──TCP:54321── Server │ │
│ │ │ │
│ │ The server responds on the CLIENT'S SOURCE PORT │ │
│ │ This port is "ephemeral" (random, temporary) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ EPHEMERAL PORT RANGES: │
│ • Linux : 32768 - 60999 │
│ • Windows : 49152 - 65535 │
│ • NAT Gateway : 1024 - 65535 │
│ │
│ SOLUTION: Allow ephemeral ports in outbound │
│ │
│ NACL Outbound Rule: │
│ Rule 100: ALLOW TCP 1024-65535 to 0.0.0.0/0 │
│ │
└─────────────────────────────────────────────────────────────┘

Comparison SG vs NACL​

┌─────────────────────────────────────────────────────────────┐
│ SECURITY GROUP vs NACL │
├─────────────────────────────────────────────────────────────┤
│ │
│ Characteristic │ Security Group │ NACL │
│ ─────────────────────┼─────────────────┼─────────────────│
│ Level │ Instance │ Subnet │
│ Type │ Stateful │ Stateless │
│ Rules │ ALLOW only │ ALLOW & DENY │
│ Evaluation │ All together │ By order │
│ Ephemeral ports │ Automatic │ Manual │
│ Default │ Deny in/Allow out│ Allow all │
│ Hot modifiable │ Yes │ Yes │
│ │
│ WHEN TO USE WHAT? │
│ ───────────────────── │
│ │
│ Security Group: │
│ ✅ Main use case │
│ ✅ Control by application/role │
│ ✅ Easier to manage │
│ │
│ NACL: │
│ ✅ Block malicious IPs (DENY rule) │
│ ✅ Additional line of defense │
│ ✅ Compliance (defense in depth) │
│ │
└─────────────────────────────────────────────────────────────┘

Practical Examples​

Create a Security Group (CLI)​

# Create the Security Group
aws ec2 create-security-group \
--group-name sg-web-server \
--description "Security group for web servers" \
--vpc-id vpc-xxx

# Add HTTP rule
aws ec2 authorize-security-group-ingress \
--group-id sg-xxx \
--protocol tcp \
--port 80 \
--cidr 0.0.0.0/0

# Add SSH rule (restrictive)
aws ec2 authorize-security-group-ingress \
--group-id sg-xxx \
--protocol tcp \
--port 22 \
--cidr 10.0.0.0/16

# Allow from another SG
aws ec2 authorize-security-group-ingress \
--group-id sg-database \
--protocol tcp \
--port 3306 \
--source-group sg-web-server

Create a NACL (CLI)​

# Create the NACL
aws ec2 create-network-acl \
--vpc-id vpc-xxx \
--tag-specifications 'ResourceType=network-acl,Tags=[{Key=Name,Value=nacl-public}]'

# Add inbound HTTP rule
aws ec2 create-network-acl-entry \
--network-acl-id acl-xxx \
--rule-number 100 \
--protocol tcp \
--port-range From=80,To=80 \
--cidr-block 0.0.0.0/0 \
--rule-action allow \
--ingress

# Add outbound rule for responses (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

Best Practices​

┌─────────────────────────────────────────────────────────────┐
│ SECURITY BEST PRACTICES │
├─────────────────────────────────────────────────────────────┤
│ │
│ SECURITY GROUPS │
│ ✅ Principle of least privilege │
│ ✅ One SG per role/function │
│ ✅ Reference SGs to each other (not IPs) │
│ ✅ Avoid 0.0.0.0/0 for SSH/RDP │
│ ✅ Document each rule (Description) │
│ │
│ NACL │
│ ✅ Keep default NACL (Allow all) │
│ ✅ Use NACL to block malicious IPs │
│ ✅ Space rule numbers (100, 200, 300...) │
│ ✅ Don't forget ephemeral ports │
│ │
│ ⚠️ COMMON MISTAKES │
│ ❌ SSH open to 0.0.0.0/0 │
│ ❌ All ports open "for testing" │
│ ❌ Forgetting outbound NACL rules │
│ ❌ Too permissive rules in production │
│ │
└─────────────────────────────────────────────────────────────┘

Validation Quiz​

  1. What is the difference between stateful and stateless?
  2. Why must ephemeral ports be configured in NACL?
  3. How to allow an app server to access a DB via SG?
  4. In what order are NACL rules evaluated?
  5. Which firewall to use to block a malicious IP?

Next section: Network Troubleshooting