Blog

Security groups have no deny rules — and that is the point

You cannot block an IP with an AWS security group. That looks like a missing feature until you notice what it buys: no rule ordering, no shadowing, and the ability to authorise a role instead of an address.

Every engineer coming to AWS from iptables asks the same question within their first week:

How do I block this IP in a security group?

You cannot. There is no deny rule. And the usual next thought — then what use is it? — is worth taking seriously, because the answer explains a genuinely different model of access control.

The premise is right, the conclusion is backwards

A security group is default-deny. There is no policy to set, no final DROP to remember. An empty security group blocks everything inbound. Anything not explicitly allowed is refused.

So invert the question. You are asking “how do I block this address?” — but the only reason that address could reach you at all is that you allowed it, almost always via a 0.0.0.0/0 rule you wrote yourself. In a default-deny allowlist, blocking is not an operation you perform. It is the state you departed from.

The analogy that holds: a security group is a guest list, not a banned-persons list. You do not strike a name off the banned list — you simply do not write it on the guest list. And there is no need for a banned list, because nobody gets in without appearing on the guest list.

The legitimate version of the question

There is a real gap here, and it is narrower than it first appears:

How do I block one address inside a range I have already allowed?

You run a public website, so port 443 is open to 0.0.0.0/0, and now one address is abusing it. A security group genuinely cannot express that. You need a different layer:

NeedLayer
Block one IP inside an allowed rangeNACL deny rule, host firewall, or WAF
Block thousands of addresses, changing constantlyipset — one rule, O(1) hash lookup
Block after N failed attemptsfail2ban or CrowdSec
Block on URL, header or payloadWAF — security groups are L3/L4 only

This is why the stack has layers. Not redundancy for its own sake — each layer expresses something the others cannot. NACLs earn their place in an AWS architecture almost entirely because they have deny rules and ordered evaluation, and security groups do not.

What you get for giving up deny

Because a security group is an unordered set of allow rules, it acquires properties that an ordered allow/deny list cannot have. If you have written iptables at any scale, you will recognise every one of these as a problem you have hit.

ORDERED RULE LIST — iptables 1 ACCEPT 10.0.0.0/8 :22 2 REJECT 0.0.0.0/0 3 ACCEPT 203.0.113.7/32 :443 Rule 3 is dead code. First match wins, and rule 2 already matched everything. Nothing errors. The rule is listed. It never fires.

UNORDERED SET — security group

ALLOW 10.0.0.0/8 :22 ALLOW 203.0.113.7/32 :443 ALLOW sg-0a1b2c3d :3306

Evaluated as a logical OR. No order exists, so no rule can shadow another. Everything else is denied by default.

Removing deny removes ordering. Removing ordering removes an entire class of production incident. and the third rule above names a GROUP, not an address

The left-hand failure is silent: the ruleset looks correct in review, and the third rule simply never matches.

Order-independence. There is no precedence and no shadowing. In iptables, inserting at the wrong index silently changes behaviour, and appending after a broad REJECT produces dead code that still appears in the listing. You cannot break a security group by reordering it, because it has no order.

No accidental allow-all. In any deny/allow list you can place a permissive rule above a restrictive one and quietly open everything while the config still reads as locked down. Structurally impossible in a set where every entry only widens access and no entry interacts with another.

Composability. Rules are additive, so several security groups on one interface produce their union. Different owners can hold different groups — a baseline maintained centrally, plus one owned by your team — and they cannot conflict. Merging two ordered allow/deny lists is ambiguous; merging two allowlists is not.

Auditability. The output of describe-security-groups is the complete answer to “what can reach this?” There is nothing else to consult and no evaluation to simulate. With an ordered list you must mentally execute it to know the effective policy, which is exactly why firewall audits are painful and security group audits are a script.

Statefulness with no return rules. Allow inbound 443 and the response leaves automatically. Compare the classic NACL mistake: allow inbound 443, forget outbound 1024-65535, and every connection hangs — because the stateless layer does not know those outbound packets are replies.

The capability that settles the argument

Here is what a security group can do that iptables, ipsets and NACLs cannot express at all:

# Database security group, inbound
Port 3306  ←  source: sg-0a1b2c3d   (the application tier's security group)

There is no IP address in that rule. It reads: any instance carrying the app-tier security group may reach MySQL. Follow the consequences:

  • It authorises a role, not a location. This is the same conceptual shift as moving from IP allowlists to workload identity — available natively in AWS, today, with no extra machinery.
  • It survives address churn completely. Auto Scaling launches three nodes with private IPs you have never seen; they can reach the database immediately, with zero rule changes. An instance is replaced and gets a new address; nothing to update.
  • It gives you microsegmentation for free. The app tier reaches the database, but two instances within the app tier still cannot reach each other unless you deliberately self-reference the group. Lateral movement between peers is denied by default.
  • The rule documents the architecture. sg-app → sg-db:3306 states intent. 10.0.2.0/24 → 3306 states a coincidence of subnet layout, and rots the moment something else lands in that subnet.

I spent a long time building automation whose entire purpose was keeping IP-based firewall rules correct as addresses changed across a fleet. Security group references make that class of problem structurally disappear. That is not a small thing.

Two limits worth knowing: references work within a VPC and across same-Region VPC peering, but not across cross-Region peering — there you fall back to CIDR blocks or an automated prefix list.

Where security groups genuinely fall short

An honest list, because this is what people are sensing when they call them limited:

You want toSG?Use instead
Deny one IP inside an allowed rangeNoNACL, ipset, WAF
Block a countryNoCDN or WAF geo-match
Rate limitNoWAF, nginx
Filter on URL, header or payloadNoWAF
Auto-ban after repeated failuresNofail2ban, CrowdSec
Hold a 50,000-entry blocklistNo — quotaipset
See what was rejectedNoVPC Flow Logs

That last row is the most operationally important and the least discussed. A security group drops traffic silently. No log, no counter, no event — nothing comparable to iptables -L -n -v packet counters or a LOG target. If you need to know that something was refused, whether for troubleshooting or detection, you must enable VPC Flow Logs and look for REJECT records.

Which is worth sitting with: an unmonitored control that has silently stopped working reports all-clear forever, and security groups are unmonitored by default.

The quotas explain the division of labour

Defaults are 60 inbound and 60 outbound rules per group, 5 groups per network interface (raisable, commonly to 16), and 1,000 rules per interface overall.

So you could not load a large blocklist into a security group even if deny rules existed. That is not an oversight — it reflects what each tool is for:

Security groupipset
SizeTens of rulesMillions of entries
ChurnRare, deliberate, reviewedConstant, automated
SemanticsAllowlist — who is authorisedBlocklist — who has misbehaved
BasisIdentity, via group referencesAddress
Managed byTerraform, in reviewfail2ban and scripts, in real time

They are not competing implementations of one idea. Trying to express “block this abusive IP” in a security group is like trying to express “the app tier may reach the database” in an ipset — imaginable, and structurally wrong.

In one line

A security group is not a firewall rule list. It is a default-deny, order-free, stateful allowlist attached to an identity.

Deny rules would destroy the order-independence and composability that make it safe, and would duplicate what three other layers already do well. What it offers instead — authorising by group rather than by address — is something none of those layers can express.

Far from useless. It is your primary access control, and the others are backstops.

References

← All posts