1 CHECKOUT · 6 CONNECTIONS · 57 CHECKS · 18 THAT LOG NOTHING

AWS VPC:
how one request crosses a private network

ONE CHECKOUT REQUEST· shop-prod · us-east-1 ·
checks it must pass·····································57
WHERE THEY HAPPEN
network ACL rules·····································22
route table lookups·····································16
security group rules·····································9
address translations·····································3
policies and firewalls·····································7
checks that log nothing when they fail·····································18

A VPC is a private address range in one AWS Region, split into subnets, with a route table, a network ACL and security groups deciding what each packet may do. This tutorial follows one checkout request through it, from the customer’s tap to the database and back, across six connections. It counts every rule, route and translation that could have dropped the request on the way, and says which log would show the failure.

The scenes play themselves, so watch or just scroll. are tappable. Every number is us-east-1, November 2026.

Sixty-five thousand addresses, chosen before the first packet

A VPC starts as one line of configuration, 10.0.0.0/16, and that line fixes 65,536 private addresses that no other customer’s traffic can reach. How do you carve that range into subnets, and which of the choices can never be undone? Nothing in this chapter decides anything for the checkout request, so Σ checks is 0 here and stays 0. Every check later in the tutorial is written in these addresses.

STEP 01 / 05

65,536 ADDRESSES YOU CANNOT RESIZE

Fig 0.1The accent slices are the eight subnets: together they use 1,568 of the 65,536 addresses on the ruler.

A VPC’s IPv4 range is one , and AWS accepts any size from /16, which is 65,536 addresses, down to /28, which is 16. shop-prod takes the largest: 10.0.0.0/16, every address from 10.0.0.0 to 10.0.255.255.

That first block is the primary CIDR, and it can never be resized. When it runs out you add a secondary block, up to five per VPC by default and 50 at most, each with subnets of its own. The shop’s eight subnets, six /24s and two /28s, use 1,568 of the 65,536 addresses: 2.4% of the range.

STEP 02 / 05

EVERY SUBNET GIVES FIVE ADDRESSES BACK

nat-a · 10.0.2.0/2816 addresses, 10.0.2.0 to .15
  1. .0network
  2. .1router
  3. .2DNS
  4. .3reserved
  5. .4free
  6. .5free
  7. .6free
  8. .7free
  9. .8free
  10. .9NAT gateway
  11. .10free
  12. .11free
  13. .12free
  14. .13free
  15. .14free
  16. .15broadcast
kept by AWS5 of 16 addresses · 31%
left for your resources11 of 16 addresses
Fig 0.2The five dimmed cells are AWS’s. The accent cell, .2, is reserved for DNS; the VPC’s resolver itself answers at 10.0.0.2.

AWS reserves five addresses in every subnet: the first four and the last. In 10.0.2.0/28 they are .0 for the network address, .1 for the VPC router, .2 for DNS, .3 for future use, and .15, the broadcast address, which AWS keeps even though VPCs do not support broadcast. The DNS resolver every subnet uses answers at 10.0.0.2, the VPC range’s base plus two.

A /24 keeps 251 of its 256 addresses, a loss under 2%. The NAT subnet is a /28, the smallest size allowed, and it keeps 11 of 16: 31% gone before anything launches. The NAT gateway takes one of the 11, 10.0.2.9.

CREATING THE NAT SUBNET, AND THE COUNT AWS RETURNS
# The smallest subnet AWS allows: a /28, 16 addresses.
aws ec2 create-subnet \
  --vpc-id vpc-0shopprod \
  --cidr-block 10.0.2.0/28 \
  --availability-zone-id use1-az1 \
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=nat-a}]'

# From the response:
#   "CidrBlock": "10.0.2.0/28",
#   "AvailabilityZoneId": "use1-az1",
#   "AvailableIpAddressCount": 11      <- 16 minus the 5 AWS reserves
STEP 03 / 05

A SUBNET NEVER SPANS TWO DATA CENTERS

shop-prod · 10.0.0.0/16
use1-az1 · us-east-1a
use1-az2 · us-east-1b
public-a10.0.0.0/24
ALB node
public-b10.0.1.0/24
ALB node
nat-a10.0.2.0/28
NAT gateway
nat-b10.0.2.16/28
NAT gateway
app-a10.0.10.0/24
app servers
app-b10.0.11.0/24
app servers
data-a10.0.20.0/24
Postgres primary
data-b10.0.21.0/24
Postgres standby
the same two AZ IDs in the fraud team’s account, 444455556666
use1-az1 · us-east-1buse1-az2 · us-east-1c
Fig 0.3Read each column’s header: the AZ ID is fixed, while the name beside it is whatever this account was assigned.

Every subnet lives in exactly one , a data center or group of data centers with its own power and networking. A subnet cannot span two, so “Multi-AZ” always means at least two subnets. shop-prod repeats each of its four tiers in two zones, eight subnets in all.

The names are not fixed. AWS can map us-east-1a to a different physical zone in each account, so your us-east-1a and the fraud team’s us-east-1a may be two facilities. The AZ ID, such as use1-az1, names the same zone in every account, and it is the value two teams agree on before placing subnets side by side.

AZ NAMES BESIDE AZ IDS, IN THIS ACCOUNT AND ANOTHER
aws ec2 describe-availability-zones \
  --region us-east-1 \
  --query 'AvailabilityZones[].[ZoneName,ZoneId]' \
  --output text

# In this account:              In another account, maybe:
#   us-east-1a  use1-az1          us-east-1a  use1-az4
#   us-east-1b  use1-az2          us-east-1b  use1-az1
#   us-east-1c  use1-az4          us-east-1c  use1-az2
# Coordinate with other teams on the ID, never the name.
STEP 04 / 05

THE VPC YOU NEVER CREATED IS PUBLIC

default VPCcreated by AWS in every Region
CIDR172.31.0.0/16
subnet size/20 · 4,096 addresses
subnets per AZ1
route to igwyes, every subnet
public IP on launchyes
shop-prodbuilt for this tutorial
CIDR10.0.0.0/16
subnet size/24 and /28
subnets per AZ4
route to igwpublic tier only
public IP on launchno
Fig 0.4Compare the last row of each card: the default VPC’s subnets assign public addresses, shop-prod’s assign none.

Every Region of every AWS account comes with a default VPC already built: 172.31.0.0/16, one /20 subnet of 4,096 addresses in each Availability Zone, and a route from every subnet to an internet gateway. Its subnets have MapPublicIpOnLaunch turned on, so each instance launched there gets a public IPv4 address unless you ask for none.

It exists because accounts created after 2013-12-04 have only VPC networking; the older EC2-Classic network shut down its last instance on 2023-08-15. shop-prod is built separately. None of its subnets hands out public addresses, and only the public tier has a route to the internet gateway.

STEP 05 / 05

TWO VPCS THAT CAN NEVER BE JOINED

shop-prod · 10.0.0.0/16shop · 111122223333
fraud team’s VPC · 10.0.0.0/16fraud scoring · 444455556666
10.0.0.010.1.0.010.2.0.010.3.0.0
65,536 addresses that exist in both VPCsroute to 10.0.10.27: shop’s app server, or fraud’s?
Fig 0.5The accent band is the overlap: every address in it exists twice, so no route table can choose between them.

The fraud-scoring team in account 444455556666 also built its VPC on 10.0.0.0/16. Two VPCs with overlapping ranges cannot be peered and cannot reach each other through a Transit Gateway, because a destination such as 10.0.10.27 would name two machines, and a route table holds one target per destination.

The only connector that works across an overlap is PrivateLink, which publishes one service through an interface in your subnet instead of joining two ranges; chapter 05 uses it. The lasting fix comes earlier: plan addresses across every account before the first VPC exists, which is what AWS IPAM pools are for.

WHAT YOU NOW KNOW

  • A VPC is one CIDR between /16 and /28; shop-prod uses 10.0.0.0/16, and its primary block can never be resized.
  • Every subnet sits in one AZ and loses five addresses, so a /28 keeps 11 of 16.
  • Overlapping VPC ranges can never be peered or routed, so plan addresses across every account first.
deep dive: secondary CIDRs, IPv6, and planning for a hundred VPCs

Secondary blocks and IPv6. A secondary IPv4 block usually comes from the same private ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) or from 100.64.0.0/10, and it cannot overlap a block already in the VPC. IPv6 works differently: AWS hands the VPC a /56, and each subnet takes a /64. One /64 holds 2^64 addresses, which is 18,446,744,073,709,551,616, so nobody sizes an IPv6 subnet. Subnets can also be IPv6-only, with no IPv4 range at all.

Planning for a hundred VPCs. A common enterprise default is a /20 per AZ per tier, 4,096 addresses, because the cost of an oversized subnet is nothing and the cost of a full one is a migration. EKS is the usual reason subnets run out: each pod takes a real VPC address. AWS IPAM hands out non-overlapping blocks from pools, so a hundred teams cannot pick 10.0.0.0/16 twice. The quotas to remember are 200 subnets per VPC and 5 VPCs per Region by default.

On the exam.

  • Two VPCs with the same CIDR must communicate → PrivateLink, or a private NAT gateway that translates through a non-overlapping range.
  • An existing VPC needs more addresses → add a secondary CIDR block; the primary block is never resized.
  • Teams in different accounts must use the same zone → coordinate on AZ IDs, never AZ names.
  • How many usable addresses in a /27 → 27: 32 minus the 5 AWS reserves.

A public subnet is one row in a route table

At 14:02:07 a customer taps Pay, and a TCP SYN addressed to 198.51.100.20 arrives at the edge of shop-prod. What makes a subnet public, and how does each packet of the request find its next hop? Counted over the first three connections (customer to load balancer, load balancer to app server, app server to Postgres), the route lookups and one address translation take Σ checks from 0 to 6.

STEP 01 / 05

THERE IS NO ‘PUBLIC’ CHECKBOX

public-rt
destinationtarget
10.0.0.0/16local
0.0.0.0/0igw-0shopedge
used by public-a and public-b
app-rt
destinationtarget
10.0.0.0/16local
0.0.0.0/0nat-0a1 (chapter 03)
used by app-a
Fig 1.1Compare the accent row: public-rt sends 0.0.0.0/0 to the internet gateway, while app-rt sends it to a NAT gateway.

A subnet is public for one reason: its has a row sending 0.0.0.0/0 to an attached to the VPC. The subnet itself has no public setting. public-rt has that row. app-rt sends 0.0.0.0/0 to a NAT gateway instead, which chapter 03 covers, so nothing on the internet can open a connection to the app tier.

Every subnet is associated with exactly one route table. A subnet with no explicit association uses the VPC’s main route table, which is why the main table should stay private. One table can serve many subnets: public-rt serves both public subnets, because both send internet traffic to the same gateway.

THE ONE ROUTE THAT MAKES PUBLIC-RT PUBLIC
aws ec2 create-internet-gateway
# -> "InternetGatewayId": "igw-0shopedge"

aws ec2 attach-internet-gateway \
  --internet-gateway-id igw-0shopedge \
  --vpc-id vpc-0shopprod

# This route is the whole difference between public-rt and app-rt.
aws ec2 create-route \
  --route-table-id rtb-0public \
  --destination-cidr-block 0.0.0.0/0 \
  --gateway-id igw-0shopedge
STEP 02 / 05

THE SERVER NEVER SEES ITS PUBLIC ADDRESS

customer
203.0.113.50
the internet
igw-0shopedge
198.51.100.20 ↔ 10.0.0.37
internet gateway
ALB node
10.0.0.37 only
public-a
packet header, as the customer sent it
src203.0.113.50
sport51234
dst198.51.100.20
dport443
Fig 1.2Watch the dst chip at the gateway: 198.51.100.20 is replaced by 10.0.0.37, and the source is untouched.

The ALB node’s has one IPv4 address, 10.0.0.37, and nothing behind it ever sees another. The public address 198.51.100.20 exists only in the internet gateway, as a one-to-one mapping to 10.0.0.37. On the way in, the gateway rewrites the destination to 10.0.0.37; on the way out, it rewrites the source back to 198.51.100.20.

That mapping is check 1 of the request. Without it the SYN never reaches the subnet, and no log anywhere records the drop. Since 2024-02-01 every public IPv4 address costs $0.005 an hour, $3.65 a month, whether it is attached to something running or allocated and idle.

STEP 03 / 05

INSIDE, A PACKET CAN BE SIX TIMES LARGER

inside the VPC9,001 bytes
through the internet gateway1,500 bytes
one jumbo frame carries 6.0 × the bytes of the largest packet the customer’s connection can use
Fig 1.3The accent bar is one jumbo frame inside the VPC. The short bar below is the most an internet gateway lets through.

The MTU is the largest packet a link carries in one piece. Inside a VPC, current instance types use jumbo frames with an MTU of 9,001 bytes, and any path through an internet gateway drops to 1,500. The ALB and the app server exchange 9,001-byte packets; the customer’s packets stop at 1,500, about one sixth the size.

Senders find the smaller limit through path MTU discovery: a hop that cannot forward a large packet returns an ICMP “fragmentation needed” message, and the sender shrinks its packets. A network ACL that drops all ICMP also drops those messages. Small requests still succeed, and large transfers stall, which is a hard failure to diagnose from either end.

STEP 04 / 05

THE LONGEST PREFIX WINS

packet to Postgresdst 10.0.20.14
app-rt
destinationtargetmatch
10.0.0.0/16local/16 ✓ wins
0.0.0.0/0nat-0a1/0 ✓
/16 fixes 16 bits, and 10.0.20.14 shares them; /0 fixes none, so it matches every address
Fig 1.4The destination is tested against every row. Two match, and the accent row wins because its prefix is longer.

A packet leaving a subnet is tested against every row of that subnet’s route table, and the most specific match wins. This is longest-prefix match: the row whose prefix fixes the most bits of the destination. For Postgres at 10.0.20.14, app-rt holds 10.0.0.0/16 → local and 0.0.0.0/0 → nat-0a1. Both match, and the /16 wins.

The local route covers the whole VPC range and cannot be deleted. Since August 2021 you can add a route more specific than it, such as 10.0.20.0/24 pointing at a firewall appliance’s interface. Traffic from the app subnet to the data subnet then passes through that appliance first.

STEP 05 / 05

THE REPLY IS ROUTED SEPARATELY

customer 203.0.113.50igw · check 1 · mapping
shop-prod · 10.0.0.0/16
use1-az1 · us-east-1a
use1-az2 · us-east-1b
public-a10.0.0.0/24
ALB nodecheck 2 · replycheck 3 · to app
public-b10.0.1.0/24
nat-a10.0.2.0/28
nat-b10.0.2.16/28
app-a10.0.10.0/24
app 10.0.10.27check 4 · replycheck 5 · to Postgres
app-b10.0.11.0/24
data-a10.0.20.0/24
Postgrescheck 6 · reply
data-b10.0.21.0/24
  1. 1internet gateway maps 198.51.100.20 → 10.0.0.37address translation
  2. 2reply: public-rt 0.0.0.0/0 → igwroute table
  3. 3public-rt 10.0.0.0/16 → localroute table
  4. 4reply: app-rt localroute table
  5. 5app-rt localroute table
  6. 6reply: data-rt localroute table
Σ checks so far6
Fig 1.5Follow checks 1 to 6 on the map: each is a lookup or a translation, and the even ones are replies.

Each packet is looked up in the route table of the subnet it leaves, so a request and its reply use two different tables. The customer’s SYN-ACK leaves through public-rt. The ALB’s packets to the app leave through public-rt, and the app’s replies through app-rt. The app’s packets to Postgres use app-rt, and Postgres replies through data-rt.

That is five route lookups and one address translation: 6 checks, and none of them writes a log line when it fails. Each lookup is made per packet, so nothing forces a reply to retrace the request’s path. Chapter 06 shows what such an asymmetric path does to a stateful firewall.

WHAT YOU NOW KNOW

  • A subnet is public only because its route table sends 0.0.0.0/0 to an internet gateway.
  • The internet gateway maps public to private addresses; the instance only ever sees 10.0.0.37.
  • Every packet uses the table of the subnet it leaves, longest prefix first: 6 checks so far.
deep dive: main route tables, more specific routes and the 500-route ceiling

Main and gateway route tables. Every VPC has a main route table, and any subnet without an explicit association uses it. Leave it with only the local route, so a forgotten subnet starts private. A gateway route table is associated with the internet gateway itself (an edge association), and it routes inbound traffic to an appliance before the traffic reaches a subnet; chapter 06 uses one. When a route’s target is deleted, such as a NAT gateway, the route stays in state blackhole and matching packets are dropped.

Quotas and public addresses. A route table holds 500 routes by default since June 2025, adjustable up to 1,000, and at most 100 routes propagated from a VPN or Direct Connect gateway, which cannot be raised. MapPublicIpOnLaunch gives an instance a public address that changes when it stops and starts; an Elastic IP stays. Public IP Insights, in VPC IPAM, lists every public IPv4 address in the account, each one $3.65 a month.

On the exam.

  • An instance has a public IP but no internet access → the subnet’s route table has no 0.0.0.0/0 route to an internet gateway.
  • Inspect traffic between two subnets in one VPC → a route more specific than local, to an appliance or a Gateway Load Balancer endpoint.
  • Reduce public IPv4 cost → fewer public addresses, IPv6, or VPC endpoints in place of internet paths.

Two filters: one tracks connections, one checks every packet

Thirteen rule evaluations stand between the customer and the database row, and ten of them are network ACL lookups. Two kinds of filter make them. A security group sits on a network interface and checks a connection once; a network ACL sits on a subnet’s edge and checks every packet, in both directions. Both decide on the same request, for different reasons, and the first three connections take Σ from 6 to 19 checks.

STEP 01 / 06

A SECURITY GROUP HAS NO DENY RULE

sg-alb · inbound
portsourceaction
tcp/4430.0.0.0/0allow
all other trafficanydropped
rules read in no order: any allow is enough
sg-alb · outbound
portdestinationaction
tcp/8080sg-appallow
all other trafficanydropped
replies to allowed traffic skip these rules
Fig 2.1Read the rule list: every row is ALLOW, and the accent line under them is the drop no rule mentions.

A is a set of allow rules attached to a network interface, not to a subnet. AWS considers every rule together, in no order, and drops any packet that matches none of them. Once a connection is allowed, its replies pass automatically, whatever the rules for the other direction say.

sg-alb, on the load balancer’s nodes, allows 443 in from anywhere and 8080 out to the app tier. Nothing else is written down, and nothing else gets through. A group holds up to 60 inbound and 60 outbound rules by default, and an interface carries up to 5 groups, or 16 after a quota increase.

STEP 02 / 06

THE RULE THAT NAMES A GROUP, NOT AN ADDRESS

sg-alb · attached to
  • ALB node · public-a10.0.0.37
  • ALB node · public-b10.0.1.84
  • next node · any new address
sg-app · inbound
portsource
tcp/8080sg-alb
IP addresses in this rule: 0
Fig 2.2The accent source field holds a group ID. Any interface in sg-alb matches it, whatever address it has today.

sg-app, on the app servers, allows TCP 8080 from sg-alb. The source is a group ID instead of a CIDR, so any network interface that carries sg-alb matches, at whatever address it has right now. The load balancer adds and removes nodes as traffic changes, each with a new private address, and the rule never needs an edit.

Group references work across peered VPCs in the same Region. Since September 2024 they also work across a Transit Gateway, for inbound rules only, once the feature is turned on for both the gateway and the VPC attachment.

ONE INBOUND RULE WHOSE SOURCE IS A GROUP ID
# The app tier accepts 8080 from anything in the load balancer's group,
# whatever addresses the load balancer has today.
aws ec2 authorize-security-group-ingress \
  --group-id sg-app \
  --ip-permissions 'IpProtocol=tcp,FromPort=8080,ToPort=8080,UserIdGroupPairs=[{GroupId=sg-alb}]'

# No CIDR anywhere in the rule. Ask for it back and you get a group ID:
aws ec2 describe-security-group-rules \
  --filters Name=group-id,Values=sg-app \
  --query 'SecurityGroupRules[?!IsEgress].[FromPort,ReferencedGroupInfo.GroupId]'
# -> [[8080, "sg-alb"]]
STEP 03 / 06

A NETWORK ACL CHECKS EVERY PACKET, BOTH WAYS

packet arriving at public-a
src203.0.113.50
sport51234
dst10.0.0.37
dport443
public-nacl · inbound
#portsourceaction
90all192.0.2.99/32DENY
100tcp/4430.0.0.0/0ALLOW
*all0.0.0.0/0DENY
rule 90 · source is not 192.0.2.99next rule
rule 100 · tcp/443 from anywherematch: ALLOW
rule *not read
Fig 2.3The packet is tested from rule 90 down. The accent row is the first match, and nothing below it is read.

A is a numbered list of allow and deny rules evaluated on every packet that enters or leaves a subnet. It works like a ticket barrier: every packet presents its ticket, in both directions, and the barrier keeps no list of who already went through.

The network ACL reads its rules lowest number first, and the first match decides; nothing below it is read. A final * rule denies whatever is left. public-nacl also carries rule 90, a deny for a scraper at 192.0.2.99/32, which no security group could express. The default quota is 20 rules per direction, raisable to 40.

STEP 04 / 06

THE REPLY NEEDS ITS OWN RULE

reply leaving public-a
src10.0.0.37
sport443
dst203.0.113.50
dport51234
public-nacl · outbound · 443 only
#portdestinationaction
100tcp/4430.0.0.0/0ALLOW
*all0.0.0.0/0DENY
dport 51234 is not 443, falls to * · reply dropped
public-nacl · outbound · ephemeral range
#portdestinationaction
100tcp/1024–655350.0.0.0/0ALLOW
*all0.0.0.0/0DENY
dport 51234 is inside 1024–65535 · reply passes
Fig 2.4Compare the two outbound rules: the first names 443 and drops the reply; the accent one names 1024–65535 and passes it.

The customer’s browser picked source port 51234 for this connection, an . The load balancer’s reply therefore leaves the public subnet addressed to port 51234, and public-nacl tests it against the outbound rules like any other packet. A rule that allows only 443 out does not match it, so the reply falls through to the final deny.

Outbound rules have to open the range clients pick from. AWS lists them: 32768–61000 on Amazon Linux, 49152–65535 on Windows Server 2008 and later, 1024–65535 for NAT gateways and load balancers. Opening 1024–65535 covers all of them. Where port 51234 comes from is in the TCP chapter of the press-Enter tutorial.

THE RULE PAIR THAT LETS HTTPS REPLIES LEAVE
# Inbound: the customer's request arrives on 443.
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0public --ingress --rule-number 100 \
  --protocol tcp --port-range From=443,To=443 \
  --cidr-block 0.0.0.0/0 --rule-action allow

# Outbound: the reply goes to the port the customer picked, e.g. 51234.
# Allowing only 443 here would drop every reply.
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0public --egress --rule-number 100 \
  --protocol tcp --port-range From=1024,To=65535 \
  --cidr-block 0.0.0.0/0 --rule-action allow
STEP 05 / 06

THE DATABASE CONNECTION NOBODY CHECKS AGAIN

app · 10.0.10.27app-a
app-nacl
data-nacl
Postgres · 10.0.20.14data-a
app-nacl out: 5432 to 10.0.20.0/23
1
network ACL evaluations
0
security group evaluations since 14:00:05
Fig 2.5Watch the two counters while queries travel: the network ACL count climbs on every packet, the security group count stays at 0.

The app opened its Postgres connection at 14:00:05, two minutes before the customer tapped Pay. sg-app outbound and sg-db inbound checked its first packet then, and recorded the connection. Every later packet on it, in either direction, matches that record and passes with no security group rule evaluated.

The network ACLs keep no such record. One query and its reply cross the edges of the app and data subnets once each way: 4 evaluations. Edit a security group rule and a tracked connection carries on until it sits idle past its timeout: 432,000 seconds (5 days) on most instance types, 350 seconds by default on Nitro v6, configurable from 60 to 432,000.

STEP 06 / 06

THIRTEEN CHECKS, TEN OF THEM NACL

C1 · customer → load balancer2 network ACL checks · 1 security group check
  1. 7public-nacl in: tcp 443 from 0.0.0.0/0network ACL
  2. 8sg-alb in: 443 from 0.0.0.0/0security group
  3. 9public-nacl out: 1024–65535 to 0.0.0.0/0network ACL
C2 · load balancer → app server4 network ACL checks · 2 security group checks
  1. 10sg-alb out: 8080 to sg-appsecurity group
  2. 11public-nacl out: 8080 to 10.0.10.0/23network ACL
  3. 12app-nacl in: 8080 from 10.0.0.0/23network ACL
  4. 13sg-app in: 8080 from sg-albsecurity group
  5. 14reply: app-nacl out: 1024–65535 to 10.0.0.0/23network ACL
  6. 15reply: public-nacl in: 1024–65535 from 10.0.10.0/23network ACL
C3 · app server → Postgres (pooled)4 network ACL checks · 0 security group checks
  1. 16app-nacl out: 5432 to 10.0.20.0/23network ACL
  2. 17data-nacl in: 5432 from 10.0.10.0/23network ACL
  3. 18reply: data-nacl out: 1024–65535 to 10.0.10.0/23network ACL
  4. 19reply: app-nacl in: 1024–65535 from 10.0.20.0/23network ACL
no security group check: tracked since 14:00:05
Σ checks so far19
Fig 2.6Read the layer tags down the ledger: ten NACL rows, three SG rows, and no SG row at all for the database.

Add up the first three connections. The network ACLs ran 10 evaluations: 2 for the customer’s connection, 4 between the load balancer and the app, and 4 for the database query. Security groups ran 3: 1 on the customer’s connection, 2 on the new load balancer connection, and none on the pooled database connection. Σ rises from 6 to 19 checks.

The gap is the price of each design. A stateless filter has to be told about both directions and pays one evaluation for every packet that crosses it. A stateful filter pays once per connection and nothing per packet afterwards, which is why sg-db never appears in this chapter’s ledger.

WHAT YOU NOW KNOW

  • Security groups are allow-only and stateful: they check a connection once, then let its replies through.
  • Network ACLs are ordered, stateless, can deny, and need ephemeral ports open for every reply.
  • C1–C3 trigger 10 NACL checks and 3 SG checks, none for the pooled database connection: 19 checks.
deep dive: rule quotas, untracked flows and timeouts

Quotas and untracked flows. The groups-per-interface quota times the rules-per-group quota cannot exceed 1,000, so raising one lowers the ceiling on the other, and a prefix list in a rule counts as its maximum number of entries. A security group skips tracking for a flow when one direction allows 0.0.0.0/0 and the other allows all ports to anywhere. Untracked flows are cut the moment a rule changes. ICMP is always tracked, and so is anything through a NAT gateway, a Network Load Balancer or a PrivateLink endpoint.

Timeouts and network ACL defaults. A tracked TCP connection stays open in the table until it idles past its timeout, which you can lower per network interface to as little as 60 seconds. The default network ACL every VPC starts with allows all traffic in and out. A new custom network ACL denies everything until you add rules, which is the usual cause of a subnet that goes silent the moment it is associated. Many teams keep their network ACLs wide open and use them only for the few deny rules a security group cannot express.

On the exam

  • “block one malicious IP” → a network ACL deny rule, since security groups cannot deny
  • “instances behind an ALB must accept traffic only from the ALB” → an inbound rule that references the ALB’s security group
  • “requests arrive but responses never reach the client” → the network ACL’s outbound ephemeral range, 1024–65535
  • “a rule was removed but connections persist” → connection tracking

Reaching the internet from a subnet with no public address

The app server at 10.0.10.27 has no public address, and it still has to reach a payment API on the internet before the customer’s Pay button returns. Its route table hands the call to a NAT gateway, which translates the source address, and the internet gateway translates it once more. The NAT gateway is billed by the hour and by the gigabyte. The payment call adds 12 checks, taking Σ from 19 to 31, the most for any connection so far.

STEP 01 / 05

ONE PACKET, TRANSLATED TWICE

  1. app serverapp-a · 10.0.10.27
  2. nat-0a1nat-a · 10.0.2.9
  3. internet gatewayEIP 198.51.100.7
  4. api.payments.example203.0.113.80:443
leaves the app server with its own address
src10.0.10.27
sport41622
dst203.0.113.80
dport443
Fig 3.1Watch the src chip change at the NAT gateway and again at the internet gateway; the API sees only the last value.

app-rt sends 0.0.0.0/0 to nat-0a1, the in the AZ1 NAT subnet. The NAT gateway replaces the source 10.0.10.27 with its own private address, 10.0.2.9, and picks a new source port. It keeps the mapping between the two, so the reply can be translated back to the app server.

The packet then leaves through the internet gateway, which maps 10.0.2.9 to the NAT gateway’s public address, 198.51.100.7, exactly as it mapped the load balancer’s address in chapter 01. The payment API sees that address and no other; 10.0.10.27 never leaves the VPC.

STEP 02 / 05

TWO ADDRESSES FOR THE PARTNER TO ALLOWLIST

api.payments.example allowlist · 198.51.100.7, 198.51.100.8internet gateway
shop-prod · 10.0.0.0/16
use1-az1 · us-east-1a
use1-az2 · us-east-1b
public-a10.0.0.0/24
public-b10.0.1.0/24
nat-a10.0.2.0/28
nat-0a1 · 10.0.2.9EIP 198.51.100.7
nat-b10.0.2.16/28
nat-0b2 · 10.0.2.25EIP 198.51.100.8
app-a10.0.10.0/24
0.0.0.0/0 → nat-0a1 ↑
app-b10.0.11.0/24
0.0.0.0/0 → nat-0b2 ↑
data-a10.0.20.0/24
data-b10.0.21.0/24
Fig 3.2Each AZ’s app subnet points at the NAT gateway in its own column; the accent chips are the two allowlisted addresses.

The payment provider accepts calls only from source addresses on its allowlist. Each zonal NAT gateway belongs to a single Availability Zone, so shop-prod runs nat-0a1 in AZ1 and nat-0b2 in AZ2, each with its own . Each AZ’s app route table points at the gateway in its own zone, and the provider allowlists both addresses.

Since 2025-11-19 a regional NAT gateway can cover several Availability Zones under one ID and needs no public subnet. shop-prod keeps two zonal ones, because chapter 06 places a firewall endpoint beside each of them.

STEP 03 / 05

55,000 CONNECTIONS TO ONE DESTINATION

connections to api.payments.example:443 from one address
this request · 1 connection55,000 · limit
the same destination with 8 addresses on the NAT gateway
1 address · 55,0008 addresses · 440,000
Fig 3.3The accent sliver is this request’s one connection to the API. The full scale is one address’s limit to a single destination.

Each NAT gateway address holds up to 55,000 simultaneous connections to one destination, meaning one IP address, port and protocol. Every connection from 10.0.2.9 to api.payments.example:443 needs a source port of its own. Once they are all in use, new connections fail, and the CloudWatch metric ErrorPortAllocation counts each one.

A NAT gateway can carry up to 8 addresses, 1 primary and 7 secondary, though only 2 Elastic IPs per gateway are allowed until you request more. Eight addresses give 440,000 connections to that same destination. Bandwidth scales on its own, from 5 Gbps to 100 Gbps.

THE METRIC THAT COUNTS CONNECTIONS WITH NO FREE PORT
# Non-zero means a new connection could not get a source port:
# one NAT address already holds 55,000 connections to that destination.
aws cloudwatch get-metric-statistics \
  --namespace AWS/NATGateway \
  --metric-name ErrorPortAllocation \
  --dimensions Name=NatGatewayId,Value=nat-0a1 \
  --start-time 2026-11-06T14:00:00Z \
  --end-time 2026-11-06T15:00:00Z \
  --period 60 \
  --statistics Sum

# The fix: give the NAT gateway another Elastic IP (up to 8 addresses),
# each worth another 55,000 connections per destination.
aws ec2 associate-nat-gateway-address \
  --nat-gateway-id nat-0a1 \
  --allocation-ids eipalloc-0second
STEP 04 / 05

4.5 CENTS FOR EVERY GIGABYTE, BOTH WAYS

shop-prod NAT · us-east-1 · per month
NAT gateway hours$65.70/month
2 gateways × 730 h × $0.045/h
Elastic IPs$7.30/month
2 addresses × 730 h × $0.005/h
data processed$0.045 per GB
in either direction, on top of internet transfer out
fixed, before any traffic$73.00/month
Fig 3.4Every line carries its unit. The accent line is the per-gigabyte charge, the one that grows with traffic.

A NAT gateway is billed on two meters. The first is time: $0.045 for every hour it exists, $32.85 a month at 730 hours, so the two zonal gateways cost $65.70 a month before a byte moves. Their two Elastic IPs add $3.65 a month each, $7.30 together.

The second meter is data. Every gigabyte the NAT gateway processes costs another $0.045, outbound or inbound, so the payment request and its response are both on the bill. That charge stacks on the normal internet data transfer rate, which is why traffic that can avoid the NAT gateway, such as the S3 upload in chapter 04, usually should.

STEP 05 / 05

NO SECURITY GROUP FITS ON A NAT GATEWAY

  1. 20sg-app out: 443 to 0.0.0.0/0security group
  2. 21app-nacl out: 443 to 0.0.0.0/0network ACL
  3. 22app-rt 0.0.0.0/0 → nat-0a1route table
  4. 23nat-nacl in: 443 from 10.0.10.0/23network ACL
  5. 24NAT gateway rewrites source to 10.0.2.9, allocates a portaddress translation
  6. 25nat-nacl out: 443 to 0.0.0.0/0network ACL
  7. 26nat-rt 0.0.0.0/0 → igwroute table
  8. 27internet gateway maps 10.0.2.9 → 198.51.100.7address translation
  9. 28reply: nat-nacl in: 1024–65535 from 0.0.0.0/0network ACL
  10. 29reply: nat-nacl out: 1024–65535 to 10.0.10.0/23network ACL
  11. 30reply: nat-rt localroute table
  12. 31reply: app-nacl in: 1024–65535 from 0.0.0.0/0network ACL
Σ checks so far31
Fig 3.5The four accent rows are all nat-nacl. No security group row appears between the app and the internet gateway.

A NAT gateway takes no security group, so the only filter in the NAT subnet is nat-nacl. It evaluates the call at four points: the request entering and leaving the subnet on its way to the API, then the response entering and leaving on its way back. sg-app checks the call once, on the first packet out of the app server.

The tally for the payment call is 6 network ACL evaluations, 3 route lookups, 2 address translations and 1 security group rule: 12 checks. Σ rises from 19 to 31 checks, and no other connection in the request has this many so far.

WHAT YOU NOW KNOW

  • A NAT gateway rewrites the source to its own address; the internet gateway then maps that to the Elastic IP.
  • Zonal NAT gateways need one per AZ; each address holds 55,000 connections to one destination.
  • NAT costs $0.045 an hour plus $0.045 per GB, and the payment call adds 12 checks: 31.
deep dive: NAT instances, egress-only gateways and the regional NAT gateway

The alternatives. A NAT instance is an EC2 instance doing the same translation, and it works only after you disable its source/destination check, which otherwise drops traffic not addressed to the instance itself. You also patch it, size it and fail it over yourself. A private NAT gateway has no Elastic IP and translates to a private address, which connects networks whose ranges overlap. IPv6 needs no NAT: an egress-only internet gateway lets connections out and blocks any opened from the internet.

The regional NAT gateway. One ID covers every AZ, in automatic or manual address mode, with up to 32 addresses per AZ. It can take up to 60 minutes to expand into a new AZ, costs $0.045 an hour per AZ, and has no private mode. Whichever kind you run, an app subnet that uses another AZ’s NAT gateway also pays $0.01 per GB in each direction for crossing between zones.

On the exam

  • “private instances need highly available outbound internet” → one NAT gateway per AZ, or a regional NAT gateway
  • “a partner allowlists our source IPs” → Elastic IPs on the NAT gateways
  • “IPv6 instances need outbound access only” → an egress-only internet gateway
  • “NAT gateway connections fail under load” → ErrorPortAllocation; add secondary addresses or spread the destinations

Reaching S3 without touching the internet

Receipts are 2 TB a month, and through the NAT gateway they would cost $92.16 in processing alone. Each receipt is an S3 PutObject from 10.0.10.27 to the shop-receipts bucket, and chapter 03’s route table sends every public address, S3’s included, out through nat-0a1. Here that upload, connection C4, gets its own route and three policies, and Σ goes from 31 to 38 checks.

STEP 01 / 05

A ROUTE THAT POINTS AT A SERVICE

app-rt · route table of app-a and app-b
destinationtarget
10.0.0.0/16local
0.0.0.0/0nat-0a1
pl-s3com.amazonaws.us-east-1.s3vpce-s3
row added by the gateway endpoint · no address, no interface
pl-s3
example ranges, kept by AWS
  • 192.0.2.0/27
  • 192.0.2.32/27
  • 192.0.2.64/27
  • … more ranges
every range is longer than /0, so it beats 0.0.0.0/0 → nat-0a1
Fig 4.1The accent row’s destination is a prefix list. It expands into the S3 address ranges listed under the table.

A for S3 adds one row to app-rt. Its destination is not a CIDR block but a , pl-s3, the set of S3 address ranges in us-east-1 that AWS keeps up to date. Its target is the endpoint, vpce-s3.

Every range in that list is longer than /0, so longest-prefix match picks it over 0.0.0.0/0 → nat-0a1 for any S3 address. The receipt upload stops crossing the NAT gateway, and the app’s code does not change. Gateway endpoints exist for two services only, S3 and DynamoDB, and AWS charges nothing for them.

STEP 02 / 05

THE SAME 2 TB FOR $0 INSTEAD OF $92

VIA NAT GATEWAY · PER MONTH
receipt PDFs uploaded2,048 GB
EC2 → S3 transfer, same Region$0.00 / month
NAT gateway processing$92.16 / month
2,048 GB × $0.045 per GB
receipts path$92.16 / month
VIA GATEWAY ENDPOINT · PER MONTH
receipt PDFs uploaded2,048 GB
EC2 → S3 transfer, same Region$0.00 / month
NAT gateway processing$0.00 / month
0 GB through the NAT gateway
receipts path$0.00 / month
Fig 4.2Both receipts move the same 2,048 GB. The only line that differs is the accent one: NAT processing.

Chapter 03’s NAT gateway charges $0.045 for every gigabyte it processes. The shop writes 2 TB of receipt PDFs a month, so sending them through nat-0a1 costs 2,048 GB × $0.045 = $92.16 a month in processing alone.

Through the gateway endpoint the same 2,048 GB cost $0. Transfer between EC2 and S3 in one Region is free on both paths, so the NAT processing line is the whole difference: same bucket, same SDK call, one route-table row apart.

STEP 03 / 05

AN ENDPOINT IS ALSO A POLICY

  • PutObject · valid role credentialsshop-receipts/r-1042.pdf✓ allowed: bucket is named
  • PutObject · valid role credentialsattacker-bucket/dump.csv✗ refused: bucket not named
endpoint policy
Effect
Allow
Principal
*
Action
s3:GetObject, s3:PutObject
Resource
arn:aws:s3:::shop-receipts/*
anything not allowed here is refused at the endpoint
Fig 4.3The accent Resource line is the whole defence: any bucket not named there is refused at the endpoint.

A gateway endpoint carries an , a JSON policy that every request through it is tested against. The default one allows every action on every resource. The policy on vpce-s3 allows two actions, s3:GetObject and s3:PutObject, on one bucket, shop-receipts, and refuses everything else by omission.

That closes a path the network checks leave open. A compromised app server holds valid role credentials and a route to all of S3, so it could copy data into a bucket in another account. Through this endpoint that write is refused, whatever the credentials allow.

THE ENDPOINT POLICY ON VPCE-0S3GATEWAY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "OnlyOurReceiptsBucket",
      "Effect": "Allow",
      "Principal": "*",
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": "arn:aws:s3:::shop-receipts/*"
    }
  ]
}
STEP 04 / 05

THE BUCKET THAT ANSWERS ONE ENDPOINT

  1. endpoint policyon vpce-s3Allow s3:PutObject on shop-receipts/*allows: bucket is named
  2. IAM role policyon role shop-appAllow s3:PutObject on shop-receipts/*allows: the role may write
  3. bucket policyon bucket shop-receiptsDeny s3:* unless aws:SourceVpce = vpce-s3no deny: request came through vpce-s3
C4 · APP SERVER → S3 RECEIPT
policy checks3
network ACL checks2
security group checks1
route table lookups1
Σ checks so far38
Fig 4.4Three policy cards stand in order. The accent card is the bucket policy, which refuses any path but this endpoint.

The bucket policy on shop-receipts answers the reverse question: which paths may reach the bucket. It denies every request whose aws:SourceVpce key is not vpce-s3, so the same valid credentials used from a laptop are refused.

An address allowlist would not work here. aws:SourceIp is absent from the request context when the caller comes through a VPC endpoint, so a condition written on public addresses never matches this request. The key for a private source address is aws:VpcSourceIp.

Add the app role’s identity policy, and three policies decide this one PutObject. With the security group rule, two network ACL rules and one route lookup, connection C4 adds 7 checks, and Σ moves from 31 to 38.

THE BUCKET POLICY ON SHOP-RECEIPTS
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "OnlyThroughShopProdEndpoint",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": ["arn:aws:s3:::shop-receipts", "arn:aws:s3:::shop-receipts/*"],
      "Condition": {
        "StringNotEquals": { "aws:SourceVpce": "vpce-s3" }
      }
    }
  ]
}
STEP 05 / 05

SECRETS GET AN ADDRESS IN YOUR SUBNET

NO ENDPOINT
secretsmanager.us-east-1.amazonaws.com
A192.0.2.150
public address · reached through nat-0a1
INTERFACE ENDPOINT · PRIVATE DNS ON
secretsmanager.us-east-1.amazonaws.com
A10.0.10.41in app-aA10.0.11.41in app-b
one interface per AZ · no NAT gateway on the path
shop-prod · 10.0.0.0/16
use1-az1 · us-east-1a
use1-az2 · us-east-1b
app-a10.0.10.0/24
app 10.0.10.27vpce-secrets 10.0.10.41
app-b10.0.11.0/24
vpce-secrets 10.0.11.41
Fig 4.5Compare the two answers for one name: the accent one is an address inside app-a, on the endpoint’s interface.

Secrets Manager has no gateway endpoint, so it uses the other kind. An interface endpoint, built on , puts a network interface with a private address into one subnet per AZ: 10.0.10.41 in app-a and 10.0.11.41 in app-b. With private DNS on, the service’s public hostname resolves to those addresses instead of a public one.

Interface endpoints are billed: $0.01 per hour per AZ, which is $14.60 a month for two AZs, plus $0.01 per GB. Each carries up to 10 Gbps per AZ and scales to 100 Gbps. The app read its database password at 14:00:05, during boot, so this endpoint adds no checks to the 14:02:07 request.

WHAT YOU NOW KNOW

  • A gateway endpoint is a free route to S3 or DynamoDB that beats the NAT route by prefix length.
  • The endpoint policy limits which buckets; the bucket policy limits which endpoint, using aws:SourceVpce.
  • One PutObject passes 3 policies and 4 network checks, taking the request to 38 checks.
deep dive: interface endpoints for S3, DynamoDB, and on-premises access

A gateway endpoint only carries traffic that starts inside its own VPC. A request from on-premises over VPN or Direct Connect, from a peered VPC, or across a Transit Gateway cannot use it. For those paths S3 also offers an interface endpoint, at interface prices ($0.01 per GB for the first petabyte a month). DynamoDB offers both kinds too. Network ACLs cannot reference a prefix list, so a network ACL that restricts S3 traffic has to list S3’s CIDR ranges itself and keep them current.

aws:SourceVpce names one endpoint; aws:SourceVpc names a whole VPC and matches any endpoint in it, so it survives replacing the endpoint. Interface endpoints take an endpoint policy as well. When a policy refuses a request, the AccessDenied message names the policy type, for example “with an explicit deny in a VPC endpoint policy”, so the error text says which of the three policies to open first.

ON THE EXAM

  • “reduce NAT gateway cost for S3 traffic” → a gateway endpoint
  • “reach S3 privately from on-premises over Direct Connect” → an S3 interface endpoint
  • “the bucket must only accept requests from our VPC” → a bucket policy Deny on aws:SourceVpce or aws:SourceVpc
  • “stop data being copied to buckets outside the account” → an endpoint policy that names your buckets

Crossing into another VPC, and another account

The fraud-scoring team in account 444455556666 also chose 10.0.0.0/16, which rules out two of the three ways to connect VPCs before you compare their prices. Peering and Transit Gateway both route between address ranges, and two identical ranges leave a route table nothing to choose between. PrivateLink routes nothing between the VPCs, so connection C6, the fraud check, goes that way. It adds 8 checks, 4 of them in the fraud team’s account: Σ goes from 38 to 46.

STEP 01 / 05

PEERING IS ONE LINK BETWEEN TWO VPCS

ABCDEFGHIJ
45 peering connections
for 10 VPCs in a full mesh: 10 × 9 ÷ 2
A → B ✓ peeredB → C ✓ peeredA → C through B ✗ not transitive
A ↔ C needs its own connection · limit 125 per VPC
Fig 5.1Count the lines: every pair of the ten VPCs needs its own. The accent pair shows A cannot reach C through B.

A connection routes traffic between exactly two VPCs whose ranges do not overlap. Each side adds a route to the other’s range that points at the connection. It is not transitive: A peered with B and B peered with C does not let A reach C.

Neither side can use the other’s internet gateway, NAT gateway, VPN or gateway endpoint either. A full mesh of 10 VPCs takes 10 × 9 ÷ 2 = 45 connections, and one VPC can hold 125 at most. Data between two VPCs in the same AZ over peering has been free since May 2021.

STEP 02 / 05

A ROUTER YOU RENT BY THE ATTACHMENT

tgw-0shop · Transit Gateway↕ one attachment per VPC
  • prod-110.1.0.0/16uses prod-rt
  • prod-210.2.0.0/16uses prod-rt
  • dev-110.11.0.0/16uses dev-rt
  • dev-210.12.0.0/16uses dev-rt
  • shared10.100.0.0/16uses shared-rt
prod-rt · for prod-1, prod-2
destinationtarget
10.1.0.0/16prod-1
10.2.0.0/16prod-2
10.100.0.0/16shared
dev rangesno route
dev-rt · for dev-1, dev-2
destinationtarget
10.11.0.0/16dev-1
10.12.0.0/16dev-2
10.100.0.0/16shared
prod rangesno route
each VPC attachment$36.50 / month
730 h × $0.05 per hour
10 VPC attachments$365.00 / month
before data: + $0.02 per GB processed
Fig 5.2Follow the two route tables: prod and dev each reach shared services, and the accent rows are the routes that don’t exist.

A is a regional router: each VPC attaches to it once, and its own route tables decide which attachments reach which. Give prod VPCs one table and dev VPCs another, put shared services in both, and leave out the routes between prod and dev. The two environments then cannot reach each other.

It is billed per attachment: $0.05 an hour, or $36.50 a month for each VPC, so 10 VPCs cost $365 a month before $0.02 per GB processed. A VPC attachment carries up to 100 Gbps per AZ, in packets up to 8,500 bytes. Like peering, it routes between ranges, so the fraud team’s identical 10.0.0.0/16 rules it out.

STEP 03 / 05

A SERVICE THAT USES YOUR ADDRESSES TOO

shop-prod10.0.0.0/16
account 111122223333
app-a · 10.0.10.0/24
app 10.0.10.27vpce-fraud 10.0.10.200
no route to the other VPC
tcp/443one way only
fraud-scoring10.0.0.0/16
account 444455556666
endpoint service vpce-svc-0fraudscore
NLB 10.0.4.12scorer 10.0.8.30
scorer sees source 10.0.4.12 · Proxy Protocol v2 carries vpce-fraud
Fig 5.3Both VPCs are 10.0.0.0/16. The accent arrow is the only path between them, and it runs one way, endpoint to load balancer.

PrivateLink works across overlapping ranges because nothing is routed between the two VPCs. The fraud team puts a Network Load Balancer behind an endpoint service, vpce-svc-0fraudscore. On the consumer side, shop-prod creates an interface endpoint whose network interface takes 10.0.10.200 in app-a. The app connects to that local address, and the endpoint forwards the connection to the load balancer.

Connections open one way only, consumer to provider, and the provider accepts each endpoint before traffic flows. The scorer sees the load balancer’s private address as the source, not 10.0.10.27. If the provider turns on Proxy Protocol v2, each connection also carries the consumer’s endpoint ID.

THE INTERFACE ENDPOINT FOR THE FRAUD SERVICE
# An interface endpoint in shop-prod for the fraud team's endpoint service.
# Both VPCs are 10.0.0.0/16; nothing is routed between them, so it works.
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-0shopprod \
  --vpc-endpoint-type Interface \
  --service-name com.amazonaws.vpce.us-east-1.vpce-svc-0fraudscore \
  --subnet-ids subnet-0appa subnet-0appb \
  --security-group-ids sg-vpce

# -> "State": "pendingAcceptance"
# No traffic flows until account 444455556666 accepts the connection.
STEP 04 / 05

EIGHT CHECKS, FOUR IN ANOTHER ACCOUNT

ACCOUNT 111122223333 · SHOP-PROD · 4 CHECKS
  1. 39sg-app out: 443 to sg-vpcesecurity group
  2. 40app-rt local (same subnet: no NACL)route table
  3. 41sg-vpce in: 443 from sg-appsecurity group
  4. 46reply: app-rt localroute table
    ACCOUNT 444455556666 · FRAUD-SCORING · 4 CHECKS
  5. 42provider NLB security group in: 443security group
  6. 43provider route table local (NLB → target)route table
  7. 44provider target security group in: from the NLBsecurity group
  8. 45reply: provider route table localroute table
Σ checks so far46
Fig 5.4The ledger splits at the accent rule: the four rows above it are in your account, the four below in the provider’s.

The endpoint interface sits in app-a, the same subnet as the app server, so C6 crosses no network ACL: those rules apply only where traffic enters or leaves a subnet. Your account evaluates 4 checks: sg-app outbound, sg-vpce inbound, and one app-rt lookup in each direction.

The fraud team’s account evaluates the other 4: the load balancer’s security group, the route lookup to the scorer, the scorer’s security group, and the reply’s route lookup. Network Load Balancers gained security groups in August 2023, with an option not to enforce them on PrivateLink traffic. Σ reaches 46.

STEP 05 / 05

THE DATA CENTER IS ANOTHER ATTACHMENT

tgw-0shop · Transit Gateway↕ attachments from the data center
Site-to-Site VPN attachment
each VPN connection = 2 tunnels
  • standard tunnel1.25 Gbps
    any VPN attachment
  • large tunnel5 Gbps
    since Nov 2025 · Transit Gateway or Cloud WAN
0 Gbps5 Gbps per tunnel
Direct Connect attachment
dedicated private circuit
joins through a Direct Connect gateway
hybrid DNS
Route 53 Resolver endpoints
inbound: on-premises asks the VPC · outbound: the VPC asks on-premises
Fig 5.5The tunnel bars are drawn to bandwidth scale. The accent bar is the large tunnel option added in November 2025.

An on-premises network joins the Transit Gateway as one more attachment. A Site-to-Site VPN connection is two encrypted tunnels over the internet, and a standard tunnel carries up to 1.25 Gbps. Since 2025-11-12 a large tunnel carries up to 5 Gbps, on Transit Gateway or Cloud WAN only. Direct Connect is a dedicated private circuit, joined through a Direct Connect gateway.

Names need a path too. A Route 53 Resolver inbound endpoint lets on-premises servers resolve names inside the VPC, and an outbound endpoint forwards the VPC’s queries for the data center’s domains to its DNS servers. None of this sits on the checkout’s path, so it adds 0 checks.

WHAT YOU NOW KNOW

  • Peering joins two non-overlapping VPCs with no transit; ten VPCs in full mesh need 45 links.
  • Transit Gateway is a billed hub with route tables for segmentation; PrivateLink is one-way and ignores overlap.
  • The fraud check adds 8 checks, 4 in another account, taking the request to 46.
deep dive: Transit Gateway route tables, shared VPCs and Cloud WAN

A Transit Gateway route table gets its routes two ways. Association decides which table an attachment’s traffic is looked up in; propagation copies an attachment’s ranges into a table automatically. Segmentation is mostly a matter of what you do not propagate, and a static blackhole route drops a range outright. Shared VPCs, through AWS RAM, answer the many-accounts problem differently: one account owns the VPC and its subnets, and participant accounts launch their own resources into them. Cloud WAN builds the same hub and route tables across Regions from one central policy.

Limits change across Regions. Inter-Region VPC peering and Transit Gateway both carry packets up to 8,500 bytes, below the 9,001 allowed inside a VPC, and Transit Gateways in two Regions connect through peering attachments. Each Resolver endpoint address handles up to 10,000 UDP queries a second, fewer when connection tracking applies. VPC Lattice is the newer, application-level option: it connects services by name across VPCs and accounts, with its own auth policies, and needs no route between the networks.

ON THE EXAM

  • “hundreds of VPCs, central routing, isolated environments” → Transit Gateway with separate route tables
  • “expose one service to many customers with overlapping CIDRs” → PrivateLink
  • “lowest cost between two VPCs in the same AZ” → VPC peering
  • “on-premises servers must resolve private hosted zones” → a Resolver inbound endpoint
  • “more than 1.25 Gbps over VPN” → ECMP across tunnels on Transit Gateway, or the 5 Gbps large tunnel

The checks you add on purpose

What can leave this VPC without any security group or network ACL evaluating it? Two kinds of traffic: DNS queries to the VPC resolver, and requests to the instance metadata service. This chapter closes both, then puts AWS Network Firewall on the payment call and gives the internet gateway a route table of its own. Each new control is a check the request must pass, so Σ climbs from 46 to 57.

STEP 01 / 05

THE PORT NO VPC FILTER CAN CLOSE

queries from 10.0.10.27 to 10.0.0.2 (VPC resolver)
  1. shop-receipts.s3.us-east-1.amazonaws.com✓ no rule matches · check 47
  2. api.payments.example✓ no rule matches · check 48
  3. score.fraud.example✓ no rule matches · check 49
  4. aGVsbG8.x.attacker.example✗ rule 100 · NXDOMAIN
rslvr-frg-0shop · rule group
priodomain listaction
100rslvr-fdl-0blocked*.attacker.exampleBLOCKNXDOMAIN
no rule matches: the resolver answers as usual
Fig 6.1Three lookups pass the rule group. The accent query, a long encoded subdomain, is the one a block rule refuses.

Every DNS query from shop-prod goes to the Route 53 VPC Resolver at 10.0.0.2. Security groups don’t filter traffic to that address, and AWS’s documentation says a network ACL can’t block DNS requests to or from the resolver either. So a compromised server can hide stolen data in the query name itself, and the resolver passes the name on to whoever runs that domain. The label aGVsbG8 is the word “hello” in base64.

closes that path from inside the VPC. A rule group associated with the VPC checks every query against domain lists, and each rule allows, alerts on or blocks a match. A blocked query gets the reply you choose, here NXDOMAIN, so the name never leaves AWS. This request’s three first lookups after boot are three checks, numbers 47 to 49.

The resolver has one more limit: 1,024 packets per second from each network interface. Past that, queries are throttled whatever the rules say, which is the first thing to check when DNS fails only under load.

A BLOCK RULE THAT ANSWERS NXDOMAIN, ATTACHED TO SHOP-PROD
# Security groups and network ACLs never see queries to 10.0.0.2.
# DNS Firewall does: every query is checked against the rule group.
aws route53resolver create-firewall-rule \
  --name block-exfil-domains \
  --firewall-rule-group-id rslvr-frg-0shop \
  --firewall-domain-list-id rslvr-fdl-0blocked \
  --priority 100 \
  --action BLOCK \
  --block-response NXDOMAIN

aws route53resolver associate-firewall-rule-group \
  --firewall-rule-group-id rslvr-frg-0shop \
  --vpc-id vpc-0shopprod --priority 101 --name shop-prod-dns
STEP 02 / 05

ONE ADDRESS EVERY INSTANCE CAN REACH

IMDSv11 request
  1. 1. GET /latest/meta-data/iam/security-credentials/shop-app→ 200 · AccessKeyId, SecretAccessKey, Token
a forwarded GET gets the same answer
IMDSv22 requests
  1. 1. PUT /latest/api/tokenX-aws-ec2-metadata-token-ttl-seconds: 21600→ 200 · session token, valid 21,600 s, sent 1 hop
  2. 2. GET /latest/meta-data/iam/security-credentials/shop-appX-aws-ec2-metadata-token: $TOKEN→ 200 · AccessKeyId, SecretAccessKey, Token
a forwarded GET has no token, and gets 401
Fig 6.2Compare the sequences: IMDSv1’s GET returns credentials in one step; the accent PUT is the step a forwarded request can’t make.

The answers at 169.254.169.254 on every instance, and one of its paths returns the instance role’s temporary credentials. Network ACLs can’t block it. Any bug that makes the server fetch a URL an attacker chose, a server-side request forgery (SSRF), can be pointed at that path.

AWS’s post introducing IMDSv2, on 2019-11-19, names that threat: SSRF through open firewalls and reverse proxies. IMDSv2 answers it in two ways. Credentials need a session token, and the token comes only from a PUT, which a forwarded GET can’t become. The token response also leaves with a hop limit, 1 by default and 2 on AMIs marked v2.0, so a proxy one network hop further away never receives it.

This request adds 0 checks here. The SDK fetched these credentials at 14:00:05, during boot, and reuses them until they expire, so Σ stays at 49.

FETCH A TOKEN, USE IT, THEN MAKE IT MANDATORY
# Step 1: a PUT, which a forwarded GET request cannot make.
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")

# Step 2: the token goes with every metadata request.
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/iam/security-credentials/

# Require tokens, and keep the token response to one network hop.
aws ec2 modify-instance-metadata-options \
  --instance-id i-0app27 \
  --http-tokens required \
  --http-put-response-hop-limit 1
STEP 03 / 05

A FIREWALL THAT READS THE SERVER NAME

TLS ClientHello, from 10.0.2.9server name (SNI)api.payments.exampleread in clear text, nothing decrypted
shop-egress · stateful rules, strict order
#actionmatch
1passtls · sni api.payments.example
2droptls · sni *
check 51: the first matching rule decides
NETWORK FIREWALL · PER MONTH
endpoint · use1-az1$288.35/month
730 h × $0.395
endpoint · use1-az2$288.35/month
730 h × $0.395
2 endpoints, before $0.065/GB$576.70/month
Fig 6.3The accent line is the allowlist entry that matched. The firewall’s monthly cost sits under it, one line per AZ.

AWS Network Firewall is a managed stateful firewall. It runs as one endpoint per Availability Zone, each in a small subnet of its own: 10.0.3.0/28 in AZ1 and 10.0.3.16/28 in AZ2. Traffic reaches an endpoint only because a route table sends it there, which is why the next step is all routes.

The shop-prod policy allows outbound TLS to a list of server names and drops the rest. The firewall reads the name from the SNI field of the TLS ClientHello, which the client sends before any encryption starts. api.payments.example matches rule 1 and passes: check 51. A connection to any other host matches rule 2 and is dropped.

The price is per endpoint-hour: $0.395, which is $288.35 a month for one AZ and $576.70 for two, before $0.065 per GB processed. By the hour, that is almost nine times what chapter 03’s two NAT gateways cost.

STEP 04 / 05

THE INTERNET GATEWAY GETS A ROUTE TABLE

↑↓
  1. igw-0shopedgeinternet gateway
    outmaps 10.0.2.9 → 198.51.100.7
    replyigw-rt 10.0.2.0/28 → vpce-fw
  2. firewall-a10.0.3.0/28 · vpce-fw
    outfw-rt 0.0.0.0/0 → igw
    replyfw-rt local → NAT gateway
  3. nat-a10.0.2.0/28 · nat-0a1
    outnat-rt 0.0.0.0/0 → vpce-fw
    replynat-rt local → app server
Σ checks so far57
Fig 6.4Follow the reply in accent: the internet gateway’s own route table sends it to the firewall before it reaches the NAT gateway.

Inserting the firewall takes three route changes. nat-rt now sends 0.0.0.0/0 to the firewall endpoint vpce-fw instead of the internet gateway, and fw-rt, the firewall subnet’s table, sends it on to the internet gateway. Outbound, the payment call now runs NAT gateway, firewall, internet gateway.

The reply is the hard part. It reaches the internet gateway addressed to the NAT gateway’s 10.0.2.9, and the VPC’s local route would deliver it straight there, past the firewall. Ingress routing closes the gap: a route table associated with the internet gateway itself, called an edge association, with one row sending 10.0.2.0/28 to vpce-fw. Without that row the firewall sees only the outbound half.

Count what the payment call gained: 4 network ACL checks on fw-nacl, 3 route lookups and 1 firewall rule, 8 in all. With the 3 DNS lookups from step 1 that makes 11, and Σ reaches 57.

STEP 05 / 05

THE FIREWALL THAT SAW HALF THE CONVERSATION

appliance mode offtgw attachment: ApplianceModeSupport=disable
tgw-0hub
firewalluse1-az1
✓ request (SYN)new flow recorded
firewalluse1-az2
✗ reply (SYN-ACK)no SYN on record: dropped
the stateful firewall in AZ2 sees half a conversation
appliance mode ontgw attachment: ApplianceModeSupport=enable
tgw-0hub
firewalluse1-az1
✓ request (SYN)new flow recorded
✓ reply (SYN-ACK)matches the flow: passed
firewalluse1-az2no traffic for this flow
both directions of the flow stay in AZ1 for its lifetime
Fig 6.5Compare the diagrams: with appliance mode off, the reply crosses AZ2’s firewall and is dropped; the accent path keeps it in AZ1.

Larger networks often run their firewalls in one central inspection VPC and send every VPC’s traffic there through a Transit Gateway. By default a transit gateway keeps traffic in the Availability Zone it came from. When the client sits in AZ1 and the server in AZ2, the request crosses the firewall in AZ1 and the reply comes back through the firewall in AZ2.

A stateful firewall that never saw the SYN treats the reply as a stray packet and drops it. Appliance mode, a setting on the transit gateway’s attachment to the inspection VPC, fixes this. It picks one AZ from the flow’s addresses and ports and keeps both directions there for the flow’s lifetime.

Third-party appliances sit behind a Gateway Load Balancer instead, which carries packets to them wrapped in GENEVE on UDP port 6081. None of this is on the checkout request’s path, so this step adds 0 checks and Σ stays at 57.

WHAT YOU NOW KNOW

  • Neither SGs nor NACLs filter DNS to the VPC resolver or the metadata service; DNS Firewall and IMDSv2 close those paths.
  • Network Firewall filters egress by hostname, and needs an internet-gateway route table to see replies.
  • The hardened request passes 11 more checks: 57 in total.
deep dive: Block Public Access, GuardDuty and centralized inspection

Block Public Access and the IMDS default. VPC Block Public Access, launched 2024-11-19, is one switch per account and Region that overrides every route table, security group and network ACL. It runs bidirectional, or ingress-only so that NAT egress keeps working, with exclusions per VPC or subnet, and it doesn’t affect Transit Gateway or Site-to-Site VPN traffic. Since March 2024 an account-level default can also make new instances IMDSv2-only. AMIs marked ImdsSupport: v2.0 launch that way, and newer instance types default to it.

Who else is watching. GuardDuty reads flow logs, DNS query logs and CloudTrail from its own copies, so it works even if you never turned those logs on. Inspection can be distributed, one firewall per VPC, or centralized in an inspection VPC behind a Transit Gateway, which then needs appliance mode. Network Firewall can also decrypt TLS to inspect payloads, at the cost of certificates and more processing; SNI filtering reads only the server name.

On the exam

  • “block DNS exfiltration”Route 53 Resolver DNS Firewall
  • “mitigate SSRF stealing instance credentials”require IMDSv2, hop limit 1
  • “allow outbound HTTPS only to approved domains”Network Firewall SNI rules
  • “stateful appliance drops return traffic behind TGW”appliance mode
  • “guarantee no VPC in the account is reachable from the internet”VPC Block Public Access

What the logs saw, and what they never recorded

A flow log record is one line per network interface, per flow, per aggregation window, and in the default format it has 14 space-separated fields. This chapter reads those lines for the checkout request, then asks where each of the 57 checks would show up if it failed. Σ holds at 57: logs record decisions and make none.

STEP 01 / 05

ONE LINE PER FLOW, PER MINUTE

  1. 2version
  2. 111122223333account-id
  3. eni-0albainterface-id
  4. 203.0.113.50srcaddr
  5. 10.0.0.37dstaddr
  6. 51234srcport
  7. 443dstport
  8. 6protocol
  9. 14packets
  10. 6230bytes
  11. 1793973720start
  12. 1793973780end
  13. ACCEPTaction
  14. OKlog-status
protocol 6 = TCPstart, end: Unix seconds, 14:02:00 to 14:03:00 UTCwindow: 60 s, set by --max-aggregation-interval
Fig 7.1Each field is labelled under its value. The accent field is action, the only one that says what a filter decided.

A record describes one flow on one network interface during one aggregation window. The ALB node’s interface, eni-0alba, records C1 as a single line: version 2, the account, the interface, both addresses and ports, protocol 6 for TCP, 14 packets, 6,230 bytes, the window’s start and end in Unix seconds, then ACCEPT and OK.

The window is 10 minutes by default, and you can set it to 1 minute when you create the flow log. On Nitro instances it is always 1 minute or less, whatever you set. A record is written only after its window closes, so a failed connection can take minutes to show up.

Read the fields again for what is missing. There is no security group name or rule number anywhere, and not one byte of payload. A flow log records what was decided at the interface, never which rule decided it.

FLOW LOGS FOR SHOP-PROD, AND TWO LINES THEY WRITE
aws ec2 create-flow-logs \
  --resource-type VPC --resource-ids vpc-0shopprod \
  --traffic-type ALL \
  --log-destination-type s3 \
  --log-destination arn:aws:s3:::shop-flow-logs \
  --max-aggregation-interval 60

# version account-id interface-id srcaddr dstaddr srcport dstport
#   protocol packets bytes start end action log-status
# 2 111122223333 eni-0alba 203.0.113.50 10.0.0.37 51234 443 6 14 6230 1793973720 1793973780 ACCEPT OK
# 2 111122223333 eni-0alba 192.0.2.99 10.0.0.37 40112 22 6 1 60 1793973720 1793973780 REJECT OK
STEP 02 / 05

AN ACCEPT, THEN A REJECT

eni-0alba · 14:02:00 to 14:03:00 UTC · check 9 removed
  1. REJECTin192.0.2.99:40112 → 10.0.0.37:221 packet · 60 bytes · a scanner tries SSH; nothing allows port 22
  2. ACCEPTin203.0.113.50:51234 → 10.0.0.37:4433 packets · 180 bytes · C1 request: network ACL and security group allow it
  3. REJECTout10.0.0.37:443 → 203.0.113.50:512343 packets · 180 bytes · C1 reply to port 51234: no outbound rule covers it
  4. ACCEPTout10.0.0.37:39418 → 10.0.10.27:808022 packets · 9114 bytes · load balancer to app server: check 11 still allows 8080
Fig 7.2Read the two accent lines as a pair: the request was accepted, the reply was rejected, so a stateless rule did it.

A flow log doesn’t name the filter that dropped a packet, but the pattern of records does. A security group is stateful: once it accepts a request, it lets the reply out without checking any rule. A rejected reply therefore needs something that checks the reply again, and in a VPC that is a network ACL.

Remove check 9, the public-nacl outbound rule for ports 1024–65535, and C1 produces exactly this pair. The customer’s request to port 443 is an inbound ACCEPT. The load balancer’s reply to port 51234 is an outbound REJECT. The customer’s browser retries, so both records count three packets.

The opposite pattern, an inbound REJECT with nothing after it, fits either filter. The scanner’s attempt on port 22 at the top of the list is one.

STEP 03 / 05

THE TRAFFIC FLOW LOGS NEVER RECORD

recorded6 of this request’s connections
  • C1customer → load balancer
  • C2load balancer → app server
  • C3app server → Postgres (pooled)
  • C4app server → S3 receipt
  • C5app server → payment API
  • C6app server → fraud service
never recordedwhoever sends it
  • shop-receipts.s3.us-east-1.amazonaws.comDNS to 10.0.0.2 · this request
  • api.payments.exampleDNS to 10.0.0.2 · this request
  • score.fraud.exampleDNS to 10.0.0.2 · this request
  • instance metadata169.254.169.254
  • Amazon Time Sync169.254.169.123
  • DHCPaddress leases
  • default routerthe subnet’s reserved .1 address
  • ARPaddress resolution
  • Windows license activationfrom Windows instances
  • mirrored trafficTraffic Mirroring copies
  • endpoint ENI ↔ NLB ENIPrivateLink’s inner hop
Fig 7.3The first list is what flow logs record. The accent rows in the second are the request’s own traffic that they skip.

Flow logs skip whole categories of traffic by design. Three are services every instance uses: the Amazon DNS server, the metadata service at 169.254.169.254 and Time Sync at 169.254.169.123. The rest are DHCP, Windows license activation, the default router’s reserved address, ARP, mirrored traffic, and traffic between an endpoint interface and a Network Load Balancer interface.

So none of this request’s three lookups appears in a flow log, and neither does the credential fetch at 14:00:05. A DNS exfiltration attempt like chapter 06’s leaves no flow log line at all. Resolver query logging records every query instead, with its name, type, response code and the instance that sent it.

STEP 04 / 05

EIGHTEEN CHECKS THAT LEAVE NO TRACE

  1. your VPC flow logs29 checks
  2. provider’s flow logs2 checks
  3. resolver query logs3 checks
  4. CloudTrail / S3 AccessDenied3 checks
  5. Network Firewall alert log1 check
  6. CloudWatch NAT metric1 check
  7. no log anywhere18 checks
Σ checks, every one mapped to a source57
Fig 7.4Each bar counts the checks whose failure that source would show. The accent bar, no log at all, is second longest.

Map each of the 57 checks to the place its failure would appear. Your own flow logs cover 29: every network ACL check and every security group check on your side. The provider’s flow logs cover 2, in the fraud team’s account. Resolver query logs cover the 3 DNS Firewall checks, and CloudTrail or the S3 AccessDenied message covers the 3 policies.

The Network Firewall alert log covers check 51, and the CloudWatch metric ErrorPortAllocation covers check 24, the NAT gateway’s port allocation. That leaves 18: the 16 route lookups and the 2 internet-gateway translations. When one of those fails, nothing writes a log line. The only clue is an ACCEPT with no reply.

STEP 05 / 05

ASK THE CONFIGURATION, NOT THE NETWORK

  1. ✓ app servereni · 10.0.10.27
  2. ✓ app-rt0.0.0.0/0 → nat-0a1
  3. ✓ nat-0a110.0.2.9 · source rewritten
  4. ✗ nat-rtno route to 0.0.0.0/0
  5. · igw-0shopedgenot reached
REACHABILITY ANALYZER · 10.0.10.27 → INTERNET, TCP 443
resultnot reachable
first blocking componentnat-rt
packets sent0 packets
price$0.10 per analysis
Fig 7.5The path is drawn hop by hop. The accent hop is where the analysis stops and names the missing route table row.

Reachability Analyzer answers a question about one path, such as “can 10.0.10.27 reach the internet on TCP 443?” It reads the route tables, network ACLs, security groups and gateways along the path, sends no packet, and reports the first component that blocks it. Each analysis costs $0.10.

Network Access Analyzer checks a whole property instead, such as “no data subnet is reachable from an internet gateway”, and lists every path that breaks it, at $0.002 per network interface analyzed. Both read configuration rather than traffic, which makes them the tools for the 18 checks that log nothing.

WHAT YOU NOW KNOW

  • A flow log line has 14 default fields; only action says what a filter decided, and never which filter.
  • DNS, IMDS, Time Sync and DHCP traffic never appear in flow logs.
  • 18 of the 57 checks leave no log line when they fail; Reachability Analyzer finds those from configuration.
deep dive: custom flow log fields, Traffic Mirroring and Athena

Custom formats. A custom format can add fields from later versions. Version 3 adds vpc-id, subnet-id, tcp-flags and pkt-srcaddr/pkt-dstaddr; version 5 adds flow-direction and traffic-path. The packet-address fields matter behind a NAT gateway. On its interface the default fields show the NAT gateway’s own 10.0.2.9 in place of one end of each connection, and only the packet addresses show the app server and the payment API behind it.

Where the lines go. Flow logs deliver to S3, CloudWatch Logs or Amazon Data Firehose, and in S3 you query them with Athena. Transit Gateway has flow logs of its own for traffic crossing its attachments. For whole packets, Traffic Mirroring copies them from an interface to a monitoring target, wrapped in VXLAN on UDP port 4789. GuardDuty derives findings from flow logs even if you never enabled them.

On the exam

  • “which component dropped the reply”ACCEPT then REJECT means a network ACL
  • “audit DNS queries”Resolver query logging, not flow logs
  • “capture packet payloads for IDS”Traffic Mirroring
  • “verify continuously that no path exists from the internet to the database”Network Access Analyzer

Fifty-seven checks, and the log that shows each one

One checkout request, six connections and three DNS lookups, counted once more. The first block sorts the 57 checks by the connection that triggered them, the second by the part of the VPC that ran them. The third is the one to keep for troubleshooting: where a failure at each check would show up, if anywhere.

ONE CHECKOUT REQUEST · SHOP-PROD · US-EAST-1
BY CONNECTION
customer → load balancer5 checks
load balancer → app server8 checks
app server → Postgres (pooled)6 checks
app server → S3 receipt7 checks
app server → payment API20 checks
app server → fraud service8 checks
DNS lookups3 checks
BY LAYER
network ACL22 checks
route table16 checks
security group9 checks
address translation3 checks
policy (endpoint, IAM, bucket)3 checks
DNS Firewall3 checks
Network Firewall1 check
WHERE A FAILURE SHOWS UP
your VPC flow logs29 checks
provider’s flow logs2 checks
resolver query logs3 checks
CloudTrail / S3 AccessDenied3 checks
Network Firewall alert log1 check
CloudWatch NAT metric1 check
no log anywhere18 checks
Σ checks passed by one checkout57
Read next. What happens when you press Enter for what the customer’s browser did before its first packet reached the internet gateway: DNS, the TCP handshake and TLS.
action ACCEPT · log-status OK