AWS for DevOps — Security Groups
3.3 — Security Groups
A security group acts as a virtual firewall for your EC2 instances. It controls inbound and outbound traffic at the instance level.
How Security Groups Work
Internet
│
▼
┌─── Security Group: web-sg ───┐
│ INBOUND RULES: │
│ Port 80 ← 0.0.0.0/0 │ (HTTP from anywhere)
│ Port 443 ← 0.0.0.0/0 │ (HTTPS from anywhere)
│ Port 22 ← 10.0.0.0/16 │ (SSH from VPC only)
│ │
│ OUTBOUND RULES: │
│ All traffic → 0.0.0.0/0 │ (allow all outbound)
│ │
│ ┌──────────┐ │
│ │ EC2 │ │
│ │ web-01 │ │
│ └──────────┘ │
└──────────────────────────────┘
Key Properties
| Property | Behaviour |
|---|---|
| Stateful | If you allow inbound traffic, the response is automatically allowed outbound (and vice versa) |
| Default deny inbound | No inbound traffic allowed unless you add a rule |
| Default allow outbound | All outbound traffic is allowed by default |
| Allow rules only | You can only create Allow rules — there is no Deny |
| Applied to ENI | Attached to the network interface, not the instance directly |
| Multiple SGs | An instance can have up to 5 security groups |
Creating a Security Group
$ aws ec2 create-security-group \
--group-name web-sg \
--description "Allow HTTP, HTTPS, and SSH from VPC" \
--vpc-id vpc-0abc123
{
"GroupId": "sg-0abc123def456"
}
# Add inbound rules
$ aws ec2 authorize-security-group-ingress \
--group-id sg-0abc123def456 \
--ip-permissions \
'IpProtocol=tcp,FromPort=80,ToPort=80,IpRanges=[{CidrIp=0.0.0.0/0,Description="HTTP from anywhere"}]' \
'IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges=[{CidrIp=0.0.0.0/0,Description="HTTPS from anywhere"}]' \
'IpProtocol=tcp,FromPort=22,ToPort=22,IpRanges=[{CidrIp=10.0.0.0/16,Description="SSH from VPC"}]'
Security Group Chaining
Instead of allowing traffic by IP, you can allow traffic from another security group. This is the recommended pattern:
┌─── ALB-sg ────┐ ┌─── App-sg ────────┐ ┌─── DB-sg ────────┐
│ In: 443 from │ │ In: 8080 from │ │ In: 5432 from │
│ 0.0.0.0/0 │─────►│ ALB-sg │─────►│ App-sg │
└───────────────┘ └────────────────────┘ └──────────────────┘
# Allow the app tier to receive traffic only from the ALB security group
$ aws ec2 authorize-security-group-ingress \
--group-id sg-app123 \
--ip-permissions \
'IpProtocol=tcp,FromPort=8080,ToPort=8080,UserIdGroupPairs=[{GroupId=sg-alb456,Description="From ALB"}]'
# Allow the database to receive traffic only from the app security group
$ aws ec2 authorize-security-group-ingress \
--group-id sg-db789 \
--ip-permissions \
'IpProtocol=tcp,FromPort=5432,ToPort=5432,UserIdGroupPairs=[{GroupId=sg-app123,Description="From App"}]'
Why chaining is better than IP-based rules:
- IPs change (Auto Scaling adds/removes instances)
- Security group references automatically apply to all current and future members
- Easier to audit (“who talks to whom?”)
Listing and Inspecting Security Groups
# List all security groups in a VPC
$ aws ec2 describe-security-groups \
--filters "Name=vpc-id,Values=vpc-0abc123" \
--query 'SecurityGroups[*].[GroupId,GroupName]' \
--output table
-------------------------------
| sg-0abc123def456 | web-sg |
| sg-app123 | app-sg |
| sg-db789 | db-sg |
-------------------------------
# Show inbound rules for a specific SG
$ aws ec2 describe-security-groups \
--group-ids sg-0abc123def456 \
--query 'SecurityGroups[0].IpPermissions'
Removing Rules
# Revoke an inbound rule
$ aws ec2 revoke-security-group-ingress \
--group-id sg-0abc123def456 \
--ip-permissions \
'IpProtocol=tcp,FromPort=22,ToPort=22,IpRanges=[{CidrIp=0.0.0.0/0}]'
DevOps Security Group Patterns
| Service | Inbound | Source |
|---|---|---|
| ALB | 80, 443 | 0.0.0.0/0 |
| Web/App instances | App port (8080) | ALB security group |
| Database (RDS) | 5432 / 3306 | App security group |
| Bastion host | 22 | Your office IP only |
| Jenkins / Build agent | 8080 | VPN CIDR |
| EFS mount target | 2049 (NFS) | App security group |
Common Mistakes
| Mistake | Why It Hurts | Fix |
|---|---|---|
Opening port 22 to 0.0.0.0/0 |
Entire internet can attempt SSH | Restrict to VPN/office IP or use SSM |
| Opening all ports for debugging, then forgetting | Wide-open attack surface | Use specific ports, clean up after debugging |
| Not using SG chaining | Rules break when IPs change | Reference security groups, not IPs |
| Modifying the default SG | It’s shared across the VPC | Create dedicated SGs for each service |
Quick Reference
| Task | Command |
|---|---|
| Create SG | aws ec2 create-security-group --group-name NAME --description DESC --vpc-id VPC |
| Add inbound rule | aws ec2 authorize-security-group-ingress --group-id SG --ip-permissions ... |
| Remove inbound rule | aws ec2 revoke-security-group-ingress --group-id SG --ip-permissions ... |
| List SGs | aws ec2 describe-security-groups --filters Name=vpc-id,Values=VPC |
| Delete SG | aws ec2 delete-security-group --group-id SG |
Next → User Data Scripts