When you launch your first EC2 instance, you quickly meet two names that sound almost the same: Security Group and Network ACL (NACL). Both filter network traffic, both use rules, and both live inside your VPC. So why does AWS give us two of them?
They protect different layers and behave differently. Once you understand that, problems like "why can't I reach my server even though my Security Group is open?" become much easier to solve.
In this article, I'll explain both in simple terms, compare them side by side, and walk through four practical examples.
How traffic flows
Internet --> [ NACL ] --> [ Security Group ] --> EC2 instance
(subnet) (instance)
Traffic from the internet meets the NACL at the subnet edge first, then the Security Group at the instance. Traffic must be allowed by both to reach your application.
What is a Security Group?
A Security Group is a virtual firewall attached to an instance (more precisely, to its network interface). It controls which traffic is allowed in and out of that resource.
- Stateful: if inbound traffic is allowed, the reply is automatically allowed out, and vice versa. You don't write a rule for the response.
- Allow rules only: you can say what is permitted, but you cannot write an explicit deny.
- All rules are evaluated: if any rule matches, the traffic is allowed.
- Defaults: a new Security Group denies all inbound traffic and allows all outbound traffic.
- Flexible sources: a rule can point to an IP range (CIDR) or to another Security Group.
- An instance can have multiple Security Groups, and one Security Group can be used by many instances.
What is a Network ACL (NACL)?
A NACL is a firewall at the subnet boundary. Every instance in the subnet is subject to it, whether or not anyone configured the instance itself.
- Stateless: inbound and outbound are judged separately. If you allow a request in, you must also allow the response out.
- Allow and Deny rules: you can explicitly block traffic, such as a single bad IP address.
-
Numbered rules: they are checked from the lowest number upward, and the first match wins. A final catch-all rule (
*) denies anything unmatched. - Defaults: the default NACL in a VPC allows all inbound and outbound traffic. A custom NACL you create denies everything until you add rules.
- Each subnet is associated with exactly one NACL, while one NACL can cover many subnets.
Security Group vs NACL: side-by-side
| Feature | Security Group | Network ACL |
|---|---|---|
| Works at | Instance / network interface level | Subnet level |
| State | Stateful (return traffic automatic) | Stateless (return traffic needs its own rule) |
| Rule types | Allow only | Allow and Deny |
| Rule evaluation | All rules evaluated together | In number order, first match wins |
| Default (new) | Inbound denied, outbound allowed | Default NACL allows all; custom NACL denies all |
| Applies to | Only resources it is attached to | Every resource in the associated subnet |
| Best used for | Day-to-day, fine-grained access control | Broad subnet guardrails and blocking specific IPs |
Easy way to remember: the NACL is the security guard at the building gate, checking everyone who enters or leaves and able to turn people away by name. The Security Group is the lock on your office door, deciding who can enter that specific room.
Example 1: A public web server (Security Group)
You launch an EC2 instance running a website. You want visitors on HTTP/HTTPS, but only you on SSH.
| Type | Protocol / Port | Source | Purpose |
|---|---|---|---|
| HTTP | TCP 80 | 0.0.0.0/0 | Public website traffic |
| HTTPS | TCP 443 | 0.0.0.0/0 | Secure website traffic |
| SSH | TCP 22 | YOUR.IP.ADDRESS/32 | Admin access from your IP only |
Because Security Groups are stateful, you don't need an outbound rule for the web page responses. They flow back automatically.
Example 2: App and database tiers (Security Group referencing a Security Group)
Your database should only talk to your application servers. Create two Security Groups:
-
app-sg: allows inbound 80/443 from the internet. -
db-sg: allows inbound TCP 3306 (MySQL) with the source set toapp-sg.
Now only instances carrying app-sg can reach the database, even if their IP addresses change. This is much cleaner than maintaining IP lists.
Example 3: Blocking a malicious IP (NACL)
Suppose your logs show repeated attack attempts from 203.0.113.50. A Security Group can't deny, so it can't block one address while keeping the rest of the internet open. A NACL can. Add a Deny rule with a lower number than your Allow rules so it is checked first:
| Rule # | Type | Port | Source | Action |
|---|---|---|---|---|
| 90 | All traffic | All | 203.0.113.50/32 | DENY |
| 100 | HTTP | 80 | 0.0.0.0/0 | ALLOW |
| 110 | HTTPS | 443 | 0.0.0.0/0 | ALLOW |
| * | All traffic | All | 0.0.0.0/0 | DENY |
Traffic from 203.0.113.50 matches rule 90 and is dropped. Everyone else continues to rules 100 and 110. Rule order matters: if the deny were numbered
Top comments (0)