Security Groups (The Instance Firewall)
A security group is a virtual firewall that sits directly in front of your EC2 (Elastic Compute Cloud — AWS’s service for renting virtual servers) instances. It decides which network traffic is allowed in and out. Every EC2 instance must have at least one security group attached, and getting these rules right is usually the difference between “my server is reachable” and “why can’t I connect?”. This page explains how security groups work, how to add rules in both the Console and the CLI (Command Line Interface), and the surprising rules that trip people up.
What a security group actually is
Think of a security group as a list of “allow” rules wrapped around one or more instances. It works at the instance level — the rules are applied to the network interface attached to each instance, not to the whole subnet. A subnet is a slice of IP addresses inside your VPC (Virtual Private Cloud — your private network in AWS).
Two ideas define how security groups behave:
- Allow-only. There is no such thing as a “deny” rule. A security group lists only the traffic you permit. Anything not explicitly allowed is blocked by default. You make something more restrictive by removing allow rules, never by adding deny rules.
- Stateful. If you allow an inbound request, the response is automatically allowed back out — you do not write a matching outbound rule for the reply. The security group “remembers” the connection.
By default a new security group allows all outbound traffic and no inbound traffic. So a fresh instance can reach the internet, but nothing on the internet can reach it until you add an inbound rule.
Gotcha — stateful vs. NACLs. A Network ACL (Access Control List) is a separate, stateless firewall that operates at the subnet level. “Stateless” means you must explicitly allow both the request and its return traffic (often on the ephemeral high port range 1024-65535). Security groups never need that — return traffic is automatic. If you only use security groups (the common case), you can ignore return-traffic rules entirely.
When to use security groups (and when not)
Use security groups as your primary, day-to-day firewall for EC2, RDS (Relational Database Service), load balancers, and most other resources that live in a VPC. They are simple, stateful, and attached right where the traffic lands.
Reach for NACLs only when you need a coarse, subnet-wide guardrail — for example, to block a specific malicious IP range across every instance in a subnet, or to add an explicit deny (which security groups cannot express). For everything else, prefer security groups: they are easier to reason about because you never juggle return traffic.
Anatomy of a rule
| Field | Meaning | Example |
|---|---|---|
| Type | A preset that fills in protocol and port | SSH, HTTP, HTTPS, Custom TCP |
| Protocol | TCP, UDP, ICMP, or All | TCP |
| Port range | The port(s) the rule covers | 22, 443, 8080 |
| Source (inbound) | Where traffic is allowed from | 203.0.113.10/32, sg-0a1b2c3d |
| Destination (outbound) | Where traffic is allowed to | 0.0.0.0/0 |
A /32 after an IP means “exactly this one address”. 0.0.0.0/0 means “anywhere on the internet (IPv4)”.
Adding an inbound rule (Console)
Say you want to allow web browsers to reach a site on your instance over HTTPS (port 443) from anywhere.
- Open the EC2 console and choose Security Groups in the left menu (under Network & Security).
- Select your security group (for example
sg-0a1b2c3d) and open the Inbound rules tab. - Click Edit inbound rules, then Add rule.
- Set Type to
HTTPS— this auto-fills ProtocolTCPand Port443. - Set Source to
Anywhere-IPv4(0.0.0.0/0). For SSH, set Source toMy IPinstead, neverAnywhere. - Add a short Description like
Public HTTPSso future you knows why the rule exists. - Click Save rules. The change takes effect within seconds — no restart needed.
Adding an inbound rule (CLI)
The same HTTPS rule with AWS CLI v2:
aws ec2 authorize-security-group-ingress \
--group-id sg-0a1b2c3d \
--ip-permissions 'IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges=[{CidrIp=0.0.0.0/0,Description="Public HTTPS"}]'
Output:
{
"Return": true,
"SecurityGroupRules": [
{
"SecurityGroupRuleId": "sgr-0c1d2e3f4a5b6c7d8",
"GroupId": "sg-0a1b2c3d",
"IsEgress": false,
"IpProtocol": "tcp",
"FromPort": 443,
"ToPort": 443,
"CidrIpv4": "0.0.0.0/0",
"Description": "Public HTTPS"
}
]
}
To remove a rule, use revoke-security-group-ingress with the same parameters (or the SecurityGroupRuleId).
Referencing another security group as a source
Instead of pinning rules to IP addresses, you can list another security group as the source. Any instance that belongs to that group is then allowed — no matter what its IP is.
This is the cleanest pattern for tiered applications. Imagine web servers in sg-web that must talk to a database in sg-db on port 3306 (MySQL). Rather than tracking every web server’s private IP, you allow the group:
aws ec2 authorize-security-group-ingress \
--group-id sg-db \
--ip-permissions 'IpProtocol=tcp,FromPort=3306,ToPort=3306,UserIdGroupPairs=[{GroupId=sg-web,Description="Allow web tier to DB"}]'
Now if you launch ten more web servers into sg-web, they instantly get database access — and nothing else does. This self-maintaining approach is far less error-prone than hardcoded /32 IP ranges.
Tip. Group-to-group references keep working even as instances scale up and down behind an Auto Scaling group. Use them for any internal traffic between your own tiers, and reserve IP-based rules for clients you do not control (like the public internet or a partner’s office).
Defining a security group as code
Here is the database group above expressed in CloudFormation, so the rules live in version control:
Resources:
DbSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Database tier
VpcId: vpc-0a1b2c3d
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
SourceSecurityGroupId: sg-web
Description: Allow web tier to DB
Cost note
Security groups themselves are free — there is no charge for creating them or for the rules. You only pay for the data transfer that the allowed traffic generates (for example, data leaving AWS to the internet). The default soft limits are generous: 60 inbound and 60 outbound rules per group, and 5 security groups per network interface, all adjustable via a quota increase request.
Best practices
- Open the narrowest port range and source you can. Never expose SSH (port 22) or RDP (port 3389) to
0.0.0.0/0; scope them to your IP or use Session Manager instead. - Prefer group-to-group references over IP ranges for traffic between your own tiers — they scale automatically.
- Add a Description to every rule so the intent survives long after you’ve forgotten it.
- Use one purpose-built group per tier (web, app, db) rather than one giant catch-all group shared by everything.
- Remember security groups are stateful: do not add outbound rules just to permit replies — they are already allowed.
- Define groups as code (CloudFormation or Terraform) so rule changes are reviewed and reversible.