Glossary

    Network ACL (NACL)

    Network Security

    A Network Access Control List (NACL) is a subnet-level firewall in a VPC. Unlike security groups, NACLs are stateless (you must create separate rules for inbound and outbound) and support both allow and deny rules.

    Security Groups vs NACLs

    • Security groups are stateful, NACLs are stateless
    • Security groups have allow-only rules, NACLs have allow and deny
    • Security groups operate at the instance level, NACLs at the subnet level
    • Security groups evaluate all rules, NACLs evaluate rules in number order (first match wins)

    Best practice: use security groups as the primary firewall and NACLs as an additional defense layer for subnet-level blocking.

    Rule Limits You Will Hit

    The default quota is 20 rules per network ACL, and that quota is enforced separately for inbound and outbound, so a default NACL gives you 20 in and 20 out. It is adjustable, but only up to 40 inbound and 40 outbound (80 total), and AWS warns that network performance might be affected at that size. You can have 200 network ACLs per VPC by default. These limits are the practical reason NACLs are a coarse instrument: you cannot express a large allowlist in them the way you can in a security group, which allows 60 inbound and 60 outbound rules by default and can go higher.

    Every subnet must be associated with exactly one NACL. If you do not associate one explicitly, the subnet uses the VPC's default NACL, which allows all inbound and all outbound traffic. Every NACL also carries a non-modifiable rule numbered with an asterisk that denies anything not matched by a preceding rule, so a freshly created custom NACL denies everything until you add rules. That is the opposite of the default NACL behavior and a frequent source of surprise. Rule numbers run from 1 to 32766, and it is worth spacing them in increments of 10 or 100 so you can insert rules later.

    How It Goes Wrong in Practice

    The single most common NACL mistake is forgetting statelessness with ephemeral ports. Someone adds an inbound allow rule for HTTPS on port 443 to a custom NACL, tests it, and the connection hangs. The inbound request arrives fine, but the response leaves from the instance on a high-numbered ephemeral source port, and the outbound rules do not allow it. Because a NACL does not track connection state, the return traffic is evaluated on its own merits and dropped. The same happens in reverse: an outbound rule for port 443 to reach an API, with no inbound rule for the ephemeral range, and the responses never come back. You need matching rules covering the ephemeral port range on the return path. The range depends on the client that initiated the request: many Linux kernels including Amazon Linux use 32768 to 61000, Windows Server 2008 and later use 49152 to 65535, and Elastic Load Balancing, NAT gateways, and Lambda functions all use 1024 to 65535.

    The second failure mode is rule numbering. NACL rules are evaluated in ascending rule number order and the first match wins, so a broad allow at rule 100 makes a specific deny at rule 200 dead code. I have seen teams add a deny rule for a malicious IP during an incident, number it 300 out of habit, and never notice it does nothing because rule 100 already allowed 0.0.0.0/0. When you use NACLs for emergency IP blocking, number the deny rules low, below any broad allow.

    Third, people try to use NACLs where security groups belong. Because NACLs cannot reference other security groups and have tight rule limits, using them for application-tier access control produces a rule set nobody can reason about. Keep NACLs for a few coarse, stable decisions: block a hostile CIDR, prevent a database subnet from ever talking to the internet, enforce a hard boundary that survives a security group mistake.

    Inspecting NACLs From the CLI

    To list every NACL in a VPC with its rules:

    aws ec2 describe-network-acls --filters Name=vpc-id,Values=vpc-0123456789abcdef0

    The output includes, per ACL, its NetworkAclId, IsDefault, the Associations (which subnets use it), and Entries: each with RuleNumber, Protocol, RuleAction (allow or deny), CidrBlock, Egress (true for outbound), and PortRange where applicable. Other useful filters are default (true or false), association.subnet-id, entry.rule-action, and entry.cidr. To find every NACL that has a deny rule, for example:

    aws ec2 describe-network-acls --filters Name=entry.rule-action,Values=deny

    Frequently Asked Questions

    Do I need NACLs if I already use security groups?

    Not for most access control, no. Security groups are stateful, easier to reason about, and can reference other security groups. NACLs earn their place as a second, independent layer: they are the control that still applies when someone misconfigures a security group, and they are the only VPC-native way to express an explicit deny for a specific CIDR.

    Why is my traffic blocked when the security group allows it?

    Traffic must pass both the NACL on the subnet and the security group on the instance. Check the NACL in both directions, and remember statelessness: an allow on the request path does not imply an allow on the response path. VPC Flow Logs will show a REJECT record, and the pattern of which direction is rejected usually tells you whether the NACL or the security group is responsible.

    What happens to existing connections when I add a deny rule?

    NACLs are stateless and evaluated per packet, so a new deny rule affects packets immediately, including packets belonging to connections that were already open. This is why NACLs are the right tool for emergency containment: a security group change does not always terminate an established connection, whereas a NACL deny stops the traffic at the subnet boundary right away.

    Related AWS Services

    Toc Consulting: AWS Security & Cloud Architecture

    Securing your AWS estate?

    Our team helps engineering teams secure and architect AWS the right way: assessment in week one, a prioritized action plan in week two.