The question this answers
Which workloads need to be addressable from the internet, and what changes for the ones that do not?
Users must reach the API from anywhere. The API must reach the database. The database must be reachable from the API and from nothing else — not from the internet, not from a scanner, not from a compromised laptop on the corporate VPN.
A placement rule with a hard consequence: workloads in a private subnet have no path from the internet at all, so an inbound attack has to compromise something else first.
A subnet is public because of its route table
There is no attribute named "public" on a subnet. A subnet is public when its route table has an entry sending 0.0.0.0/0 to an internet gateway, and private when it does not. Everything else — that instances in it may have public addresses, that it can be reached from outside — follows from that one line. This is worth being pedantic about, because it means the property can be changed by a single route edit, and it means a subnet you believe is private is only private if you have actually read its route table.
The consequence for a private workload is total and cheap. There is no path for an inbound packet to take. A scanner sweeping the provider's public ranges cannot find it. A credential-stuffing bot cannot try passwords against it. A zero-day in the database's wire protocol cannot be exploited by anyone who has not already got inside the network. None of that required a rule, an agent or a subscription — it required the absence of a route.
The canonical layout falls straight out of it: the load balancer is in the public subnet because it must be dialable, and everything else is not. The application does not need a public address because the load balancer reaches it over the network's local route. The database does not need one because the application reaches it the same way. Adding a public address to either buys nothing and costs the entire benefit.
Which workloads actually need a public address
The test is narrow: does something outside your network need to *initiate* a connection to this specific resource? Almost nothing does. A public entry point does — a load balancer, an API gateway, a CDN origin that cannot be private. A bastion or session-manager endpoint might, though a managed access service usually removes even that. Everything else is reached either through the entry point or over the network's local routes.
Note that "it needs to call out to the internet" is a different requirement and does *not* justify a public address. Outbound-initiated traffic is what a NAT Gateway or a private endpoint is for, and it preserves the no-inbound-path property. Conflating the two is how application instances end up with public addresses "so they can reach the payment API" — which grants inbound reachability the workload never needed.
Judge exposure in context rather than by reflex. A load balancer answering 443 from the whole internet is correct and should not be flagged. A database answering 5432 from the whole internet is a finding, and the fact that it has a strong password does not make it not a finding. See Public Exposure, Read With Context.
| Workload | Needs inbound from the internet? | Placement | Verdict on a public address |
|---|---|---|---|
| Load balancer / API gateway | Yes — that is its entire job. | Public subnet | Correct. This is the design, not an exposure finding. |
| Web or API application instances | No — the entry point reaches them locally. | Private subnet | Unnecessary. Remove it; it only adds an attack surface. |
| Background workers and queue consumers | No — they pull work. | Private subnet | Never justified. |
| Relational database | No — only the application talks to it. | Private data subnet | A finding. A public 5432 is the classic breach headline. |
| Cache / message broker | No. | Private subnet | A finding, and historically the source of some of the largest data exposures. |
| NAT device | No inbound; it needs a public address to translate to. | Public subnet | Correct, and its state table is what blocks inbound. |
| Bastion / jump host | Sometimes — prefer a managed access service instead. | Public subnet if unavoidable | Tolerable with strong controls; better removed entirely. |
What the private tier costs you
Private placement is not free, and pretending otherwise is how teams get surprised. A private workload that needs to reach anything outside — a package mirror, a container registry, a payment API, a telemetry endpoint — needs an egress path, and that path is a billed component with its own capacity limits and its own failure modes. One NAT device per zone is the correct reliability answer and it multiplies the fixed cost by the number of zones.
It also costs access. You can no longer SSH to an instance from your laptop, which is a security improvement and an operational inconvenience; the replacement is a managed session service or a bastion, both of which are more infrastructure. And debugging changes shape: a private workload that cannot reach the internet fails in ways that look identical to a firewall problem, because both produce a hang.
For a genuinely stateless public service with no private data — a static site, a stateless renderer — a single public subnet with tight security groups is a defensible design and saves all of this. The public/private split earns its cost when there is something behind the entry point worth keeping unreachable.
Bars are relative weights, not currency. Real rates depend on provider, region, commitment and volume.
Key points
- A subnet is public if and only if its route table sends 0.0.0.0/0 to an internet gateway — there is no other attribute.
- Private placement removes the inbound path entirely, which defeats scanning and remote exploitation without any rule being evaluated.
- Needing to call out is not a reason for a public address; that is what a NAT device or private endpoint is for.
- Exposure is judged in context: a public load balancer on 443 is the design, a public database on 5432 is the finding.
- The private tier costs an egress path with its own bill and failure modes, plus the loss of direct SSH access.
The loop, answered
Every field is required, which is why no lesson here can recommend something without saying what it costs and what simpler thing to consider first.
- • Every subnet is associated with exactly one route table, which decides where non-local traffic goes.
- • A public subnet's table sends 0.0.0.0/0 to an internet gateway; resources there additionally need a public address to be reachable.
- • A private subnet's table has no such entry, so an inbound packet has no path in and an outbound one has no path out.
- • Local routes covering the network's own range exist in every table, which is why tiers reach each other without configuration.
- • Egress for the private tier is added by pointing 0.0.0.0/0 at a NAT device in a public subnet, preserving the no-inbound property.
- • Own the placement rule per tier and enforce it in infrastructure code, so placement is not decided per deployment.
- • Own subnet sizing per zone, remembering that container platforms consume addresses far faster than virtual machines.
- • Own the periodic exposure review: enumerate every resource with a public address and justify it.
- • Own the replacement for SSH — a managed session service, audited, rather than a bastion everyone shares.
- • Own the egress design, because a private subnet without one produces failures that look like firewall problems.
- • An instance in a public subnet with no public address: unreachable from outside, reachable from inside, and it looks like a firewall problem.
- • A private subnet with no egress route, so package installs and API calls hang at start-up while the instance itself is healthy.
- • A route added to a private subnet during debugging and left there, silently making a data tier internet-reachable.
- • Address exhaustion in the private subnet, blocking deploys while every existing instance keeps running.
- • A single-zone private subnet, so a zone failure removes a whole tier even though other zones have capacity.
- • Private subnets need to be sized generously per zone; the fleet grows into them and the range cannot be widened afterwards.
- • Each zone needs its own private subnet and its own egress path, so the design scales by repetition rather than by growth.
- • Egress capacity scales separately from the fleet: doubling the instances doubles the outbound connections through one NAT device.
- • Public subnets stay small — they hold entry points, not fleets.
- • The strongest control available: no inbound path means no inbound attack, independent of software versions or credentials.
- • It is a layer, not authentication. A compromised application instance can still reach the database exactly as designed. See Infrastructure Trust Boundaries.
- • Removing public addresses from application instances shrinks the attack surface to the entry points, which is a much smaller set to defend and to audit.
- • Default-deny outbound from the private tier is the next step most environments skip, and it is what turns a compromise into a contained one. See Public Exposure, Read With Context.
- • The split itself is free; the egress path it requires is not.
- • NAT devices bill per hour and per gigabyte, and reliability multiplies the hourly part by the zone count.
- • Private endpoints for provider services usually pay for themselves by removing traffic from the NAT meter.
- • The access replacement — a managed session service or bastion — is a small but permanent line item.
- • A recurring inventory of public addresses and internet-facing security-group rules, diffed over time.
- • Available addresses per subnet, per zone, alerted before exhaustion rather than during a deploy.
- • Flow-log rejects at the private tier boundary, which show what is trying to reach it and failing.
- • The signal that lies: an instance health check that passes. It proves the process is alive, not that anything can reach it or that it can reach anything.
- • A single public subnet with strict security groups, for a stateless service with no private data — cheaper and simpler, and defensible.
- • A platform-as-a-service that owns the network, when the application has nothing private to place.
- • Private endpoints instead of a NAT device, when the only outbound needs are provider services — cheaper and it removes a component.
- • No egress at all for a worker that reads a queue and writes a database over private endpoints. Not every private subnet needs a NAT.
- • Buys the removal of an entire class of attack; costs a metered egress path, an access-service dependency and harder debugging.
- • Buys clean tier boundaries that document themselves; costs an address plan with per-zone repetition.
- • Buys audit simplicity — the public list is short; costs the convenience of reaching a machine directly when something is wrong.
Public, private, and which one is actually a finding
Public exposure scanner: the port is not the verdict
What people believe, and what is true
Private subnet means the workload is secure.
It means unreachable from the internet. Lateral movement, stolen credentials and a compromised peer inside the same network are all unaffected by it.
The application needs a public address to call external APIs.
Outbound-initiated traffic goes through a NAT device or a private endpoint. A public address grants inbound reachability the workload never asked for.
Anything public is a security finding.
A load balancer on 443 and a NAT device are public by design. The finding is a public data store, an open admin port, or an entry point nobody can explain.