When configuring firewall rules on AWS, you're dealing with two distinct layers of network security:
AWS Security Groups operate at the instance level (EC2) and act as stateful virtual firewalls. They evaluate all traffic before it reaches your instance.
iptables/UFW operates at the OS level, filtering packets after they've reached the network interface but before they're processed by applications.
// Security Group Example (AWS CLI)
aws ec2 authorize-security-group-ingress \
--group-id sg-903004f8 \
--protocol tcp \
--port 22 \
--cidr 203.0.113.0/24
# UFW Example (Linux)
sudo ufw allow proto tcp from 203.0.113.0/24 to any port 22
Security Groups:
- Processed by AWS hypervisor before traffic reaches your instance
- No CPU overhead on your instance
- Rules have slight propagation delay (seconds)
iptables/UFW:
- Processed by your instance's kernel
- Adds minimal CPU overhead (~1-2% for basic rules)
- Changes take effect immediately
For most AWS deployments, I recommend using both with this approach:
# AWS Security Group (minimum required ports only)
- SSH (22) from your IP
- HTTP (80)
- HTTPS (443)
# iptables/UFW (additional hardening)
sudo ufw default deny incoming
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Security Groups are better for:
- Cluster-wide rules
- Rules that need to apply to multiple instances
- Environments where you can't modify OS configurations
iptables/UFW are better for:
- Instance-specific rules (like limiting SSH to a jump host)
- Complex packet filtering (like rate limiting)
- Additional logging requirements
1. Overlapping rules causing confusion
2. Forgetting that Security Groups are stateful (return traffic is automatically allowed)
3. Not testing changes in staging first
4. Locking yourself out (always keep a backup SSH session open when changing rules)
When configuring network security for AWS EC2 instances, you're essentially dealing with two distinct layers of protection:
1. AWS Security Groups (Cloud-level protection)
2. iptables/UFW (OS-level protection)
AWS Security Groups:
- Stateful firewall operating at the instance/ENI level
- Rules defined by protocol/port ranges and CIDR blocks
- Evaluates all traffic before it reaches your instance
- No packet processing overhead on your instance
iptables/UFW:
- Kernel-level packet filtering framework
- Operates on the actual network interfaces of your OS
- Supports complex rule chains and advanced filtering
- Adds processing overhead but provides finer control
Here's how you might implement both approaches for a web server:
# AWS Security Group (CloudFormation snippet)
WebServerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow web and SSH access
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 203.0.113.0/24
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
# UFW configuration on Ubuntu
sudo ufw allow proto tcp from 203.0.113.0/24 to any port 22
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
The dual-layer approach provides:
- Defense in depth: An attacker would need to bypass both layers
- Reduced attack surface: Cloud-level filtering stops unwanted traffic before it reaches your instance
- Compliance benefits: Some standards require host-based firewall controls
Use Security Groups alone when:
- You trust AWS's network isolation
- Instance performance is critical
- You're using managed services that handle OS security
Add iptables/UFW when:
- You need complex filtering rules (e.g., rate limiting)
- Compliance requires host-based controls
- You're running multi-tenant services on a single instance
For most production workloads, I recommend:
- Set restrictive Security Groups as your first line of defense
- Use iptables/UFW for additional controls and logging
- Consider tools like AWS Network ACLs for subnet-level protection
Here's a sample iptables script with enhanced logging:
#!/bin/bash
# Flush existing rules
iptables -F
# Set default policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Allow established connections
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Allow loopback
iptables -A INPUT -i lo -j ACCEPT
# SSH from specific subnet
iptables -A INPUT -p tcp --dport 22 -s 203.0.113.0/24 -j ACCEPT
# Web traffic
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# Log dropped packets
iptables -A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables_DROP: " --log-level 7
# Save rules (Debian/Ubuntu)
iptables-save > /etc/iptables.rules
Regularly review both security layers:
# Check Security Group assignments
aws ec2 describe-security-groups --group-ids sg-xxxxxxxx
# Monitor iptables traffic
sudo iptables -L -v -n
# UFW status
sudo ufw status numbered