AWS for DevOps — Security Groups

3 minute read

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

Updated: