System designCore9 min

Private networks: NAT, VPCs & firewalls

Most machines in a system have addresses nobody on the internet can reach, and that is on purpose. Private address ranges, NAT, virtual private clouds, subnets and firewall rules decide which machines face the world, which talk only to each other, and how anything gets out.

1 · The idea

Most machines have an address the internet cannot reach

A bookkeeping service for small businesses runs a load balancer, a dozen app servers and a PostgreSQL database holding every customer's accounts. Only one of those needs to be reachable from the internet: the load balancer. The database, especially, should be impossible to even try to connect to from outside.

The tool is private addresses: ranges reserved for internal networks, which internet routers are expected to filter out. Traffic addressed to them never crosses the public internet. Three ranges were reserved in 1996 and are still used everywhere, from home Wi-Fi to cloud networks:

Range

Written as

Addresses

10.0.0.0 – 10.255.255.255

10.0.0.0/8

About 16.8 million

172.16.0.0 – 172.31.255.255

172.16.0.0/12

About 1 million

192.168.0.0 – 192.168.255.255

192.168.0.0/16

65,536

Private addresses were also an answer to scarcity: IPv4 has only about 4.3 billion addresses in total, far fewer than the devices that want one. Any number of private networks can reuse the same ranges, as long as something translates at the edge.

2 · NAT

NAT lets many private machines share one public address

When an app server at 10.0.2.15 calls the bank's API to import a customer's transactions, the bank must be able to reply. NAT (network address translation) rewrites the outgoing packet's source to a public address and a port it picks, remembers the mapping, and translates the reply back:

Replies match a mapping and are let in. A connection that nobody inside started has no mapping, so it goes nowhere.

Three consequences matter in design:

Consequence

Why it matters

Inbound is blocked by default

A side effect, not a security policy: it shouldn't be the only thing between the database and the internet

Ports are finite

AWS's NAT gateway allows 55,000 simultaneous connections per public address to each unique destination; more addresses raise the limit

It can fail

It lives in one zone; if that zone fails, every zone using it loses outbound access. Use one per zone

NAT is also why peer-to-peer calls are hard (WebRTC & peer-to-peer) and why servers that must accept connections need a public address or a load balancer in front. With IPv6 there are enough addresses for every device, and clouds offer an egress-only gateway that allows outbound connections without NAT while still refusing unsolicited inbound ones.

3 · VPCs

A VPC is your own private network inside someone else's data center

In the cloud, the bookkeeping service gets a virtual private cloud (VPC): a network logically isolated from every other customer's, with an address range it chooses, such as 10.0.0.0/16 (65,536 addresses). Inside it:

  • Subnets are smaller ranges, such as 10.0.1.0/24 (256 addresses). In AWS each subnet lives in one availability zone; in Google Cloud the VPC is global and subnets are regional.
  • Route tables decide where traffic for each destination range goes: stay inside the VPC, go to the internet gateway, go to a NAT gateway, or go to a connected network.
  • An internet gateway connects the VPC to the internet for the subnets whose routes point to it.

Whether a subnet is public or private is decided by one route. A public subnet has a route for all internet traffic (0.0.0.0/0) to the internet gateway, and its machines can have public addresses. A private subnet has no such route; its machines can reach the internet only through a NAT gateway sitting in a public subnet, and nothing on the internet can start a connection to them.

Route table of

Traffic for

Goes to

Public subnet

10.0.0.0/16, inside the VPC

Stays local

Public subnet

0.0.0.0/0, anywhere else

The internet gateway

Private subnet

10.0.0.0/16, inside the VPC

Stays local

Private subnet

0.0.0.0/0, anywhere else

The NAT gateway, outbound only

4 · Layout

Public subnets face the internet; private subnets only face each other

The bookkeeping service splits its tiers by subnet, repeated in each availability zone for redundancy (Redundancy & failure domains):

CustomersPublic subnetload balancer, NAT gatewayPrivate app subnetapp serversPrivate data subnetdatabase, no internetHTTPS 4438080, from LB only5432, from app onlyoutbound via NATCustomersPublic subnetload balancer, NAT gatewayPrivate app subnetapp serversPrivate data subnetdatabase, no internetHTTPS 4438080, from LB only5432, from app onlyoutbound via NAT

Only the load balancer and the NAT gateway have public addresses. App servers can call out through NAT but can't be called from outside; the database can't reach the internet at all.

Each tier can only be reached from the one in front of it. An attacker who finds a bug in the load balancer's configuration still can't connect to the database directly, because there is no route from the internet to its subnet and no rule that would admit them. That layering is defense in depth: several independent barriers, any one of which might fail on its own.

5 · Firewall rules

A firewall rule says who may talk to whom, on which port

Routes decide where traffic can go; firewall rules decide whether it is allowed. Clouds provide two kinds, and AWS's are typical:

Aspect

Security group

Network ACL

Applies to

Each instance (network interface)

A whole subnet

Rules

Allow only; everything else is denied

Allow and deny, checked in numbered order

Replies

Allowed automatically (stateful)

Must be allowed explicitly (stateless)

Best used as

The main control, rule by rule

A coarse guard rail, such as blocking a range

Security groups can name other security groups instead of addresses, which matters when addresses change with every deploy (Service discovery & registration). The bookkeeping service's rules read like the architecture itself:

Group

Allows in

From

Load balancer

TCP 443

Anywhere

App servers

TCP 8080

The load balancer group only

Database

TCP 5432

The app server group only

Keep rules as narrow as the architecture allows: specific ports, specific source groups, no "allow all from 0.0.0.0/0" on anything but the front door. Google Cloud's firewall works the same way: stateful, with ingress denied unless a rule allows it and egress allowed by default.

6 · In and out

A forward proxy speaks for clients; a reverse proxy speaks for servers

Two kinds of middleman sit at the edges of a private network, facing opposite directions:

App serversForward proxyallow-list of destinationsInternetReverse proxyload balancer, gatewayoutgoing callsonly approved hostsincoming requestsforwarded

A reverse proxy accepts requests for your servers (Reverse proxies & API gateways). A forward proxy makes requests on behalf of your servers, so outbound traffic can be checked against an allow-list and logged.

Controlling egress is easy to forget and valuable: it limits where stolen data can be sent and stops a server from being tricked into calling internal addresses (Webhooks covers this for user-supplied URLs). Cloud services can be reached without the internet at all: private endpoints, such as AWS PrivateLink, make a cloud service appear at a private address inside the VPC, so traffic to object storage or a queue never needs the NAT gateway.

Private networks also connect to each other. VPC peering joins two VPCs; a transit gateway joins many; a site-to-site VPN or a dedicated line connects a VPC to an office or data center. Each connection is another path an attacker could use, so give it the same narrow rules as anything else.

7 · In practice

Put only the front door in public, and everything else behind it

Administrators still need to reach private servers. The old answer is a bastion host, one hardened machine in a public subnet that admins log in to first; managed tools such as AWS Systems Manager Session Manager open sessions through the provider's own service, with no inbound port, bastion or SSH key to manage, and log every session. Either way, admin access should be logged, short-lived and tied to a person (Network segmentation & zero trust goes further).

  1. Plan the address ranges

    Choose VPC ranges that won't overlap with offices or other VPCs you may connect later; renumbering a live network is painful.

  2. Public only at the edge

    Load balancers and NAT gateways in public subnets; app servers and data stores in private ones.

  3. Narrow rules

    Security groups that reference each other, specific ports, nothing open to the world but the front door.

  4. Control the way out

    NAT per availability zone, private endpoints for cloud services, and a forward proxy where outbound calls must be restricted.

  5. Watch it

    Flow logs of what connects to what, and alerts on rules that open anything to 0.0.0.0/0.

Check yourself

4 questions

1. An app server in a private subnet can call an external API but can't be reached from the internet. What makes that possible?
2. What makes a subnet public?
3. A security group allows inbound TCP 5432 from the app servers. Does it also need a rule for the database's replies?
4. All app servers in three availability zones share one NAT gateway in zone A. Zone A fails. What happens?

Takeaways

Remember this

  • Private address ranges are filtered from the internet, so machines on them can only be reached from inside or through a translator.
  • NAT lets private machines make outbound connections and drops unsolicited inbound ones; it has port and availability limits of its own.
  • A VPC divides into subnets whose route tables make them public or private; only the front door belongs in a public subnet.
  • Stateful security groups that reference each other express "who may talk to whom on which port"; stateless network ACLs add coarse guard rails.
  • Control the way out as well as the way in: NAT per zone, private endpoints for cloud services, and a forward proxy for restricted egress.