Secure EC2 with security groups
Learn to secure EC2 with security groups in this Cloud security essentials tutorial. Step-by-step, hands-on, with troubleshooting and next steps.
Focus: secure ec2 with security groups
You SSH'd into your EC2 instance and noticed a process you never started. Or worse — your database was dropped and you're staring at a ransom note. The root cause of most EC2 compromises isn't weak passwords or a missing patch; it's an overly permissive security group that leaves port 22 or 3306 open to the entire internet (0.0.0.0/0). Security groups are your first line of defense in the cloud — the virtual firewall between your instances and the wild. But if you don't configure them with intent, they're like leaving your car doors unlocked in a busy parking lot. This lesson shows you exactly how to secure EC2 with security groups: from mental model to step-by-step configuration, hands-on exercises, and gotchas that bite even experienced engineers.
The problem this lesson solves
Say you launch an EC2 instance with the default security group. It allows all inbound traffic from anywhere, and all outbound traffic to anywhere. It works — but it also means anyone who finds your IP can attempt to connect to every open port. The result: exposed databases, cryptojacking, and data breaches that could be prevented with a few rules.
The core problem is implicit trust: developers often leave default rules in place "to make things work," then forget about them. Security groups don't scan for vulnerabilities; they only filter traffic. If you open port 22 to 0.0.0.0/0, you're inviting bots that scan the entire IPv4 space to brute-force your SSH key. In 2024, automated attacks hit new instances within minutes of launch. Without proper security group rules, you're not just vulnerable — you're a target.
Core concept / mental model
Think of a security group as a bouncer at a club or a physical firewall around your instance. It has a simple set of rules: who can come in (inbound) and who can leave (outbound). But unlike a traditional firewall, security groups are stateful: if you allow an inbound request, the response is automatically allowed, regardless of outbound rules. That's convenient, but it also means you must be precise about what you allow in.
Key attributes:
- Inbound rules: control traffic arriving at your instance (e.g., SSH on port 22, HTTP on port 80).
- Outbound rules: control traffic leaving your instance (e.g., to update packages or call an API).
- Reference by IP, CIDR, or other security groups: You can allow traffic from a specific IP (e.g., your office), a range (e.g., a VPC CIDR), or another security group (e.g., a load balancer).
- Default deny: By default, all inbound traffic is denied and all outbound is allowed. You must explicitly open ports.
Pro tip: Security groups are stateful — if you allow inbound SSH from your IP, the response packets are automatically allowed back out. You don't need a separate outbound rule for that. But if you want to block outbound traffic (e.g., to prevent data exfiltration), you must explicitly deny it with a restrictive outbound rule.
How it works step by step
Here's the mental flow for securing any EC2 instance:
- Identify what services need to be exposed. Only expose what's necessary. For a web server, you need port 80 (HTTP) and maybe 443 (HTTPS). For SSH, you need port 22 — but only from your IP or a bastion host.
- Choose the most restrictive source. Instead of
0.0.0.0/0, use a specific CIDR like203.0.113.10/32(your office IP) or a security group ID for internal services. - Create a new security group (or modify an existing one) with minimal inbound rules.
- Attach the security group to your instance (you can do this at launch or later via the EC2 console or CLI).
- Test your rules: verify that allowed traffic works and that denied traffic is blocked.
- Audit regularly: security groups accumulate rules over time; remove unused ones.
The cause-and-effect is clear: a rule = access. Every open port is a potential attack surface. The fewer rules, the smaller the blast radius if the instance is compromised.
Hands-on walkthrough
Let's practice securing an EC2 instance with security groups using the AWS CLI. We'll create a security group that only allows SSH from your IP and HTTP from anywhere (for a web server), then attach it to an instance.
Step 1: Get your current IP
First, find your public IP. Use a service like checkip.amazonaws.com:
MY_IP=$(curl -s https://checkip.amazonaws.com)
echo $MY_IP
Expected output: a public IP like 203.0.113.42. Use this as your CIDR.
Step 2: Create the security group
# Replace with your VPC ID
VPC_ID=vpc-0abc123def4567890
SG_NAME=web-server-sg
SG_DESC="Web server security group"
SG_ID=$(aws ec2 create-security-group \
--group-name $SG_NAME \
--description $SG_DESC \
--vpc-id $VPC_ID \
--query 'GroupId' \
--output text)
echo "Created security group $SG_ID"
Expected output: Created security group sg-0a1b2c3d4e5f67890
Step 3: Add inbound rules
Now add rules. First, SSH from your IP only:
aws ec2 authorize-security-group-ingress \
--group-id $SG_ID \
--protocol tcp \
--port 22 \
--cidr "${MY_IP}/32"
echo "SSH rule added for $MY_IP/32"
Then allow HTTP from anywhere (for a public web server):
aws ec2 authorize-security-group-ingress \
--group-id $SG_ID \
--protocol tcp \
--port 80 \
--cidr 0.0.0.0/0
echo "HTTP rule added for 0.0.0.0/0"
Expected output: no output from the authorize command, but the echo lines confirm.
Step 4: Attach the security group to an instance
If you're launching a new instance, specify the security group at launch:
aws ec2 run-instances \
--image-id ami-0abcdef1234567890 \
--instance-type t2.micro \
--key-name my-key-pair \
--security-group-ids $SG_ID \
--subnet-id subnet-0abc123def4567890
To attach to an existing instance, use modify-instance-attribute:
INSTANCE_ID=i-0abc123def4567890
aws ec2 modify-instance-attribute \
--instance-id $INSTANCE_ID \
--groups $SG_ID
Step 5: Verify your rules
Check the rules you've set:
aws ec2 describe-security-groups --group-ids $SG_ID
Expected output: JSON with IpPermissions showing only port 22 from your IP and port 80 from all IPv4.
If you later want to remove a rule (e.g., close SSH), use revoke-security-group-ingress:
aws ec2 revoke-security-group-ingress \
--group-id $SG_ID \
--protocol tcp \
--port 22 \
--cidr "${MY_IP}/32"
Now your instance is accessible only on the ports you explicitly opened — a much smaller attack surface.
Pro tip: If you can't SSH into your instance after this exercise, double-check that your IP hasn't changed (e.g., if you're on a VPN). The rule is locked to your original IP.
Compare options / when to choose what
You have several ways to control access to EC2. Here's how they compare:
| Method | Scope | Advantages | Disadvantages | When to use |
|---|---|---|---|---|
| Security groups | Per instance/eni | Stateful, easy to manage, supports referencing other SGs | Cannot deny specific IPs (only allow rules) | Default choice for most workloads |
| Network ACLs | Per subnet | Stateless, can deny traffic, cost-effective at subnet level | Must manage separate inbound/outbound rules, not stateful | Extra layer for complex VPC designs |
| AWS WAF | Application layer (HTTP/HTTPS) | Filters web traffic (SQLi, XSS) | Extra cost, only for web traffic | Protecting web applications |
| Bastion host / SSM Session Manager | Access management | No SSH exposure, audit trails, dynamic IPs | Requires extra setup | Replacing SSH for admin access |
Choose security groups when you need simple, stateful filtering per instance. Add Network ACLs when you want a second layer of defense at the subnet level. Use SSM Session Manager to eliminate SSH exposure altogether.
Troubleshooting & edge cases
Here are common pitfalls and how to fix them:
- Can't connect to your instance after adding a rule. Check that the security group is actually attached to the instance (
aws ec2 describe-instance-attribute --instance-id i-... --attribute groupSet). Also verify the rule's CIDR matches your current IP. - You set an outbound deny-all rule but can't update packages. Security groups are stateful, but if you block all outbound, responses to new outbound requests are also blocked. Allow outbound on port 443 for
yum/aptupdates, or set your CIDR to the OS update servers. - Security group rule for another SG references a group that doesn't exist. AWS validates SG references at rule creation time. If you delete the referenced SG later, the rule becomes invalid. Clean up rules when removing SGs.
- Port 22 open to
0.0.0.0/0because you 'forgot'. Audit your rules periodically. Useaws ec2 describe-security-groupsand grep for0.0.0.0/0to find dangerous rules. Automate this with a script in CI. - You're using a public IP for SSH, but your IP changes often. Switch to SSM Session Manager or a bastion host with a fixed IP. Alternatively, use a narrow CIDR like your office's /24.
What you learned & what's next
You now understand how to secure EC2 with security groups: what they are, how to create and attach them, and how to avoid common pitfalls. You can explain the core idea (stateful virtual firewall), complete a practical exercise (creating rules via CLI), and compare them with other access control tools. This is a critical skill for any cloud engineer.
Next in the track: In the next lesson, you'll learn how to apply these security group principles to a multi-tier architecture — for example, placing web servers in public subnets and databases in private subnets, using security groups to control traffic between tiers. That builds on the foundation you just laid.
Keep your instance locked down, audit your rules quarterly, and always ask: "Does this port really need to be open to the world?"
Practice recap
Practice by creating a security group that allows SSH only from your current IP and HTTP from anywhere, attach it to a test instance, and confirm that SSH from a different IP fails. Then remove the SSH rule and verify you can no longer connect — this builds muscle memory for least privilege.
Common mistakes
- Leaving port 22 open to
0.0.0.0/0— bots will brute-force it within minutes. Restrict SSH to your IP or use SSM Session Manager. - Adding inbound rules that reference a security group that gets deleted later, breaking the rule without warning.
- Forgetting that security groups are stateful — blocking outbound doesn't automatically break responses to allowed inbound requests, but it can block new outbound calls like package updates.
- Using overly broad CIDRs (like 0.0.0.0/0) for internal services like databases — use a private subnet or other security group references instead.
- Not auditing security groups after making changes — rules accumulate and create a bloated attack surface.
Variations
- Use AWS Systems Manager Session Manager instead of SSH — no inbound port 22 needed, and you get audit logs.
- Add a Network ACL (NACL) on your subnets for a second, stateless layer of defense.
- Use AWS WAF when you need application-layer filtering (e.g., for SQL injection) on your EC2 web servers.
Real-world use cases
- Locking down an SSH-only management instance for a development team by allowing only the office IP range.
- Designing a web tier that allows HTTP/HTTPS from anywhere but restricts database traffic to the web security group only.
- Automating security group audits in a CI pipeline to catch any rules open to 0.0.0.0/0 before deployment.
Key takeaways
- Security groups are stateful virtual firewalls; they default to deny all inbound and allow all outbound.
- Always use the most restrictive source CIDR or security group reference — never open 0.0.0.0/0 unless absolutely required.
- You can attach a security group to an existing instance without recreating it.
- Audit your security groups regularly and remove unused rules to minimize attack surface.
- For admin access, prefer SSM Session Manager to eliminate SSH exposure altogether.
- Security groups are just one layer — combine them with Network ACLs and WAF for defense in depth.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.