T Techclick ← All lessons
Amazon · AWS security · Lesson 3 of 10

SG vs NACL: stateful allow on the ENI, numbered allow+deny on the subnet

Priya opened 443 on sg-web. SYN-ACK still died. The subnet NACL acl-app allowed inbound 443 and forgot outbound ephemeral ports. Security groups are stateful and allow-only. NACLs are stateless, numbered, and can deny. Official compare page — not folklore.

Updated 2026-08-18·18 min read·L2 primary·Quiz at end

After this page you can

The ticket

ALB health checks fail. sg-web allows 443 from the ALB SG. Instance ENI is in the app subnet. Custom NACL acl-app has inbound 443 allow, outbound only 443 allow. Return traffic to the ALB high ports is dropped. Junior “fixed” it by 0.0.0.0/0 on the SG. The NACL was the hole.

Quick interview answer

Official VPC compare: a security group is stateful, attached to an ENI/instance, allow rules only, all matching allows are evaluated. A network ACL is stateless, attached to a subnet, allow and deny, rules processed in number order (lowest first) until a match, then implicit deny. Return traffic is automatic on an SG. On a NACL you must allow the return path yourself (typically high/ephemeral ports outbound for a server). Default NACL allows all; a custom NACL denies all until you add rules.

Hero · two layers
Security group on the ENI and network ACL on the subnet
SG hugs the ENI. NACL hugs the subnet. Both can drop you.
Lab data · dummy only

Account 111122223333 · OUs Security / Workloads · hub VPC 10.50.0.0/16 · app VPC 10.51.0.0/16 · SG sg-web · NACL acl-app · bucket tc-lab-app-logs · role AppEC2Role · TGW tgw-lab · VGW public 203.0.113.60 · on-prem FortiGate WAN 203.0.113.10. Dummy account ID. Not yours.

Mental model

Security groupNetwork ACL
StateStateful — return allowed automaticallyStateless — return needs a rule
WhereENI / instance (sg-web, sg-app)Subnet (acl-app)
RulesAllow onlyAllow and deny, numbered
EvalAll allows consideredLowest number that matches wins
This lab443 from ALB SG into sg-web; app ports only from sg-web into sg-appacl-app on 10.51.0.0/16 subnets — do not forget ephemeral out
Packet meets both
Client acl-appsubnet · stateless sg-webENI · stateful instance

NACL first at the subnet, then SG on the ENI. Fail either layer and the ticket stays open.

https://111122223333.signin.aws.amazon.com/console
Training mock · not live
VPCIAMS3CloudTrail
VPC → Security groups / Network ACLs

sg-web vs acl-app

Inbound 443 from ALB SG · egress least-privilege · stateful
Inbound app port from sg-web only · no 0.0.0.0/0
100 allow 443 · numbered, first match
110 allow ephemeral return · stateless — required
NACL can deny a bad prefix; SG cannot write a deny
Official SG vs NACL compare. Training mock.

How to choose

NeedUseSkip when
Allow this instance to take 443 from the ALBsg-web allow from the ALB SG id0.0.0.0/0 “just for now”
Hard deny a prefix for a whole subnetNumbered DENY on acl-app above the allowTrying to write a deny on an SG
Default-allow lab subnetLeave the default NACL (allow all) and do the work in SGsCustom NACL with no ephemeral return

Runbook

  1. Side A — ENI

    Describe sg-web and sg-app. Inbound should reference SG ids, not the internet, unless that is the design. SGs are allow-only — if it is too open, delete the allow.

  2. Side B — subnet

    Describe acl-app. Read rule numbers low to high. Confirm inbound and outbound both exist for the flow. Add ephemeral outbound for a server.

  3. Side C — prove

    Reach 443. If SG looks right and it still dies, it is NACL, route, or the target. Do not open 0.0.0.0/0 as a diagnostic.

aws cli · SG + NACL
aws ec2 describe-security-groups --group-names sg-web sg-app \
  --query 'SecurityGroups[].{GroupName:GroupName,IpPermissions:IpPermissions,IpPermissionsEgress:IpPermissionsEgress}'

aws ec2 describe-network-acls --filters Name=tag:Name,Values=acl-app \
  --query 'NetworkAcls[].Entries'

# Custom NACL: inbound 443 allow is not enough.
# Outbound must allow the return ephemeral range you designed.
# Default NACL already allows all — custom starts deny-all.

Four failures

1 · Custom NACL, no return

Classic. Stateless. Ephemeral outbound (or the exact return ports) must exist.

2 · Deny on a security group

There is no deny. Remove the allow, or use a NACL deny for a prefix.

3 · 0.0.0.0/0 on sg-web to “test”

It ships. Source the ALB / sg-web instead.

4 · Rule 200 allow, rule 100 deny forgotten

NACL first-match by number. The lower number already decided.

How to prove it

Close only when

1) Both SGs show the intended allows only. 2) acl-app inbound and outbound match the flow. 3) You did not add 0.0.0.0/0. 4) You can say stateful vs stateless without hedging.

Traps

SymptomLook atWrong fix
SYN in, no SYN-ACKNACL outbound / route / target SGWide SG allow
Works then fails after NACL “hardening”Missing ephemeralDisable the SG
“SG deny this IP”Cannot — use NACL deny or do not allowInvent a deny checkbox

Knowledge check

Judgment items. One best answer. Reasons send you back to the matching section.

Q1

Security groups are…

Correct: b. Official compare.
Q2

A custom NACL allows inbound 443 only. Why does the browser stall?

Correct: b. Stateless return.
Q3

You need to hard-deny one prefix for every ENI in the subnet. Tool?

Correct: b. NACL can deny.
Q4

Default NACL vs custom NACL?

Correct: a. VPC NACL guide.
Q5

sg-app should accept the app port from…

Correct: b. Reference the peer SG.
Q6

NACL rule 100 deny 10.0.0.0/8, rule 200 allow 443. A host in 10.1.1.1:443…

Correct: b. Number order.

AWS security class series: Shared + IAM · Org / OU · SG vs NACL · Role not key · KMS + Secrets · VPC endpoint · Trail + GuardDuty · TGW hub · S3 public · Interview

Sources

Related: AWS session factory · Migration series.