Cloud Networking
Building networks in the cloud: virtual networks and subnets, security groups, internet and NAT gateways, peering, and private connections to your own network.
Virtual networks
When you create a server in the cloud, it has to live in a network. That network is not a set of switches you can touch but a software construct: a virtual private cloud (VPC), a logically isolated network that the provider builds for you on top of its own physical network. Inside it you choose the address range, cut it into subnets, decide the routes and control which traffic is allowed. Other customers' networks run on the same hardware, but their traffic cannot reach yours unless you connect the networks on purpose.
Everything you know from the Networking topic still applies: Internet Protocol (IP) addresses, prefixes, routing, network address translation (NAT) and firewalls. What changes is that you configure these with an application programming interface (API) call, a web console or a template instead of on a router, and that the provider takes care of the physical layer, redundancy and the switching in between.
| Concept | AWS | Microsoft Azure | Google Cloud |
|---|---|---|---|
| Virtual network | VPC (one region) | Virtual network, VNet (one region) | VPC network (global) |
| Subnet scope | One availability zone | All zones of the region | One region, all its zones |
| Instance firewall | Security group | Network security group (NSG) | VPC firewall rules and firewall policies |
| Outbound NAT | NAT gateway | NAT Gateway | Cloud NAT |
| Dedicated private link | Direct Connect | ExpressRoute | Cloud Interconnect |
| Private access to managed services | VPC endpoints (PrivateLink) | Private Endpoint (Private Link) | Private Service Connect |
The scope differences matter when you design for resilience. In AWS a subnet lives in exactly one availability zone (AZ), so you create at least one subnet per zone. In Azure a subnet spans the whole region and you pick the zone per resource. A Google Cloud VPC network is global: one network can have subnets in many regions.
Planning addresses with CIDR
You give the virtual network a private address range in Classless
Inter-Domain Routing (CIDR) notation, such as
10.0.0.0/16 (65,536 addresses), and carve subnets out
of it. The arithmetic is exactly the
subnetting you already
know. Three rules make the difference between a plan that lasts and
one that has to be rebuilt:
- Never overlap with your on-premises networks, your other VPCs or a partner you may connect to. Two networks with the same addresses cannot be peered or joined by a VPN without address translation, and changing a VPC's range later means moving every resource in it.
- Leave room. A range is hard to grow; unused private addresses cost nothing. Reserve spare blocks for new zones, new tiers and container networks, which consume addresses quickly.
- Remember the reserved addresses. AWS and Azure keep five addresses of every subnet for themselves (in AWS: the network address, the router, the Domain Name System (DNS) server, one for future use and the last address; in Azure: the network address, the default gateway, two for Azure DNS and the broadcast address); Google Cloud keeps four.
Python's ipaddress module checks a plan before you
build it. Here the head office already uses
10.50.0.0/16, so the first candidate is rejected; the
second is split into /20 blocks for a public and a private subnet in
each of two zones:
import ipaddress
on_premises = [
ipaddress.ip_network("10.50.0.0/16"), # head office data center
ipaddress.ip_network("192.168.10.0/24"), # branch office
]
# Two candidate ranges for the new VPC: which one is free?
for candidate in ("10.50.0.0/16", "10.0.0.0/16"):
network = ipaddress.ip_network(candidate)
clashes = [str(n) for n in on_premises if network.overlaps(n)]
print(f"{candidate:13} ->", f"overlaps {', '.join(clashes)}" if clashes else "free")
vpc = ipaddress.ip_network("10.0.0.0/16")
blocks = vpc.subnets(new_prefix=20) # sixteen /20 blocks, handed out in order
plan = {}
for tier in ("public", "private"):
for zone in ("a", "b"):
plan[f"{tier}-{zone}"] = next(blocks)
RESERVED = 5 # AWS and Azure keep 5 addresses of every subnet for themselves
for name, subnet in plan.items():
usable = subnet.num_addresses - RESERVED
print(f"{name:10} {str(subnet):14} {usable:>5} usable")
spare = list(blocks)
print(f"Spare /20 blocks: {len(spare)}, the next one is {spare[0]}")
10.50.0.0/16 -> overlaps 10.50.0.0/16 10.0.0.0/16 -> free public-a 10.0.0.0/20 4091 usable public-b 10.0.16.0/20 4091 usable private-a 10.0.32.0/20 4091 usable private-b 10.0.48.0/20 4091 usable Spare /20 blocks: 12, the next one is 10.0.64.0/20
Three quarters of the range stays free for later. Many organizations keep a central list (an IP address management tool, or simply a reviewed file in version control) from which every new VPC gets its own, non-overlapping block.
Public and private subnets
Nothing inside a subnet makes it public or private. The difference is its route: a public subnet has a route to the internet through an internet gateway, and its instances can have a public IP address. A private subnet has no such route; its instances only have private addresses and cannot be reached from the internet at all. The usual pattern puts only the entry points — load balancers, and NAT gateways — in public subnets, and everything else (application servers, databases) in private ones.
Route tables
A route table is the virtual router's list of destinations and where to send traffic for each. Every subnet is associated with one route table, and many subnets can share the same one. Each table automatically contains a local route for the VPC's own range, so all subnets in a VPC can reach each other; you add routes for everything else. The lookup works like the longest prefix match on a router:
| Route table | Destination | Target |
|---|---|---|
| Public (both zones) | 10.0.0.0/16 |
local |
0.0.0.0/0 |
Internet gateway | |
| Private, zone a | 10.0.0.0/16 |
local |
0.0.0.0/0 |
NAT gateway in zone a | |
10.50.0.0/16 |
VPN or dedicated link to the head office |
Azure and Google Cloud work the same way with slightly different names: Azure adds system routes automatically and lets you override them with a route table of user-defined routes; Google Cloud has system-generated routes and custom routes on the VPC network.
Internet and NAT gateways
Internet gateway
An internet gateway is the VPC's door to the internet: a redundant, provider-managed component that you attach to the VPC and use as a route target. For an instance with a public IP address it also performs the one-to-one translation between that public address and the instance's private address — the instance itself only ever sees its private address. Traffic can flow in both directions, but only for resources that have a public address and whose firewall rules allow it.
Not every provider has a separate resource for this. In Google Cloud
the default internet gateway is simply a next hop in a
route. Azure has no internet gateway object at all: a system route
sends 0.0.0.0/0 to the internet, and a virtual machine
reaches it through a public IP address, a NAT gateway or a load
balancer. New virtual networks now create private subnets without
this implicit "default outbound access" (since 2026, at the time of
writing), so plan explicit outbound connectivity; existing virtual
machines keep their access.
NAT gateway
Servers in private subnets still need to reach the internet now and then — to download updates, call an external API or send email. A NAT gateway is a managed service that translates their private source addresses to its own public address, exactly like the port address translation on a home router. It is outbound only: replies to connections the servers opened come back in, but nobody on the internet can start a connection to them through it.
In AWS a NAT gateway lives in one public subnet of one zone. Put one in every zone and point each private subnet at the gateway in its own zone; with a single shared gateway, a zone failure cuts outbound traffic for every zone, and traffic crossing zones adds data transfer charges. NAT gateways are billed per hour and per gigabyte processed, which makes them a surprisingly common line on the bill; see Cloud Costs and FinOps.
Security groups and network ACLs
Routes decide where traffic can go; firewall rules decide whether it may. Cloud networks have two layers of them.
Security groups
A security group is a virtual firewall attached to an instance's network interface (not to the subnet). It holds allow rules only: anything no rule allows is dropped. It is stateful: when a connection is allowed in one direction, the replies are allowed automatically, without a rule for the return traffic. A new AWS security group allows all outbound traffic and no inbound traffic.
Rules can name another security group as the source instead of an address range: "allow port 5432 from the group app-servers". The database then accepts connections from every instance in that group, whatever its IP address, which keeps rules correct as instances come and go with autoscaling.
Network ACLs
A network access control list (network ACL or NACL) in AWS sits at the subnet boundary and filters everything that enters or leaves the subnet. It works like an access control list on a router: numbered rules, checked from the lowest number up, the first match wins, and rules can allow or deny. It is stateless: return traffic is checked like any other packet, so if you allow inbound Hypertext Transfer Protocol Secure (HTTPS, port 443) you must also allow the outbound replies to the client's ephemeral ports (AWS advises 1024–65535, because client operating systems use different ranges). The default network ACL allows everything; a newly created custom one denies everything until you add rules.
| Security group | Network ACL | |
|---|---|---|
| Applies to | Instance (network interface) | Subnet |
| State | Stateful: replies allowed automatically | Stateless: both directions need rules |
| Rules | Allow only | Allow and deny |
| Evaluation | All rules together; any match allows | In number order; first match decides |
| Typical use | The main, fine-grained control per tier | Coarse extra layer, such as blocking an address range |
Azure and Google Cloud blend the two. An Azure network security group (NSG) is stateful, has allow and deny rules with priorities, and can be attached to a subnet, a network interface or both. Google Cloud's VPC firewall rules are stateful, support allow and deny with priorities, and apply to instances selected by network tag or service account.
A security group in Python
This simulation shows both properties: an allow-list for new inbound
connections, and a connection-tracking table that lets replies back
in. The web server allows HTTPS from anywhere and Secure Shell (SSH)
only from the office range 203.0.113.0/24:
import ipaddress
# Inbound allow rules of the web server's security group.
# Outbound: everything allowed (the usual default).
INBOUND_RULES = [
{"protocol": "tcp", "port": 443, "source": "0.0.0.0/0"}, # HTTPS from anywhere
{"protocol": "tcp", "port": 22, "source": "203.0.113.0/24"}, # SSH from the office
]
tracked = set() # connection-tracking table: this is what makes it stateful
def inbound(protocol, src, sport, dport):
if (protocol, src, sport, dport) in tracked:
return "allow: reply to a tracked connection"
for rule in INBOUND_RULES:
if (rule["protocol"] == protocol and rule["port"] == dport
and ipaddress.ip_address(src) in ipaddress.ip_network(rule["source"])):
return f"allow: rule {protocol}/{dport} from {rule['source']}"
return "drop: no rule allows it"
def outbound(protocol, dst, sport, dport):
tracked.add((protocol, dst, dport, sport)) # expect the reply from dst:dport
return "allow: all outbound allowed"
print("Visitor -> 443 ", inbound("tcp", "198.51.100.25", 51000, 443))
print("Visitor -> 22 ", inbound("tcp", "198.51.100.25", 51001, 22))
print("Office -> 22 ", inbound("tcp", "203.0.113.40", 52000, 22))
print("Visitor -> 3306 ", inbound("tcp", "198.51.100.25", 51002, 3306))
print("Server -> API ", outbound("tcp", "192.0.2.80", 40000, 443))
print("API reply ", inbound("tcp", "192.0.2.80", 443, 40000))
print("Stranger -> 40000", inbound("tcp", "192.0.2.99", 443, 40000))
Visitor -> 443 allow: rule tcp/443 from 0.0.0.0/0 Visitor -> 22 drop: no rule allows it Office -> 22 allow: rule tcp/22 from 203.0.113.0/24 Visitor -> 3306 drop: no rule allows it Server -> API allow: all outbound allowed API reply allow: reply to a tracked connection Stranger -> 40000 drop: no rule allows it
The reply from the API gets in although no inbound rule mentions
port 40000, because it matches a connection the server opened. A
packet from another host to the same port does not match anything
and is dropped. A stateless network ACL has no tracked
table: it would need an explicit inbound rule for ports 1024–65535
to let that reply in.
Peering and transit hubs
Organizations soon have more than one virtual network: one per application, per environment (development, test, production) or per team. VPC peering connects two virtual networks directly over the provider's backbone, so their instances talk using private addresses as if they were one network. It is called VNet peering in Azure and VPC Network Peering in Google Cloud. Two rules catch people out:
- The ranges must not overlap — another reason for the address plan above.
- Peering is not transitive. If A is peered with B and B with C, A cannot reach C through B. Every pair that must talk needs its own peering, and the number of connections grows quickly: ten networks that all talk to each other need 45 peerings.
At that scale a hub-and-spoke design takes over: a central transit hub connects all networks and the links to on-premises, and routes between them, often through a central firewall. Examples are AWS Transit Gateway, Azure Virtual WAN (or a hub VNet you build yourself) and Google Cloud's Network Connectivity Center.
Private connections to your own network
In a hybrid setup the cloud network and the office or data center must reach each other. A private connection links them so that traffic between private addresses on both sides does not travel openly over the internet. There are two kinds.
Site-to-site VPN
A site-to-site virtual private network (VPN) builds encrypted Internet Protocol Security (IPsec) tunnels over the internet between a gateway in the cloud (AWS Site-to-Site VPN, Azure VPN Gateway, Google Cloud VPN) and a firewall or router on your premises. It is quick to set up and inexpensive, but its bandwidth and latency depend on the internet path. Providers usually give you two tunnels so one can fail. The VPNs section of Networking explains the tunnels themselves.
Dedicated links
A dedicated link is a physical connection from your network to the provider, usually set up in a colocation data center or through a telecom partner: AWS Direct Connect, Azure ExpressRoute and Google Cloud Interconnect. Traffic never crosses the public internet, so bandwidth is predictable and latency consistent. The trade-offs are cost, lead time (weeks rather than minutes) and the need for a second link for redundancy. A dedicated link is private but not automatically encrypted; organizations that need encryption run IPsec or MACsec (link-layer encryption) over it. A VPN is often kept as a backup path.
Private endpoints for managed services
Managed services such as object storage or managed databases have public endpoints by default. A private endpoint gives such a service an address inside your own VPC (or, for AWS gateway endpoints and Google's Private Google Access, a private route to it), so instances in private subnets reach it without a NAT gateway or internet access, and you can block the public endpoint entirely. AWS calls these VPC endpoints (interface endpoints through PrivateLink, plus gateway endpoints for S3 and DynamoDB), Azure calls them Private Endpoints, and Google Cloud uses Private Service Connect and Private Google Access.
Load balancers
A cloud load balancer is the usual front door of an application: it sits in the public subnets, accepts connections from the internet and spreads them over healthy instances in the private subnets of several zones. Providers offer layer 7 load balancers, which understand the Hypertext Transfer Protocol (HTTP) and can route by path or host name (AWS Application Load Balancer, Azure Application Gateway, Google Cloud's Application Load Balancer), and layer 4 ones that forward Transmission Control Protocol (TCP) and User Datagram Protocol (UDP) (AWS Network Load Balancer, Azure Load Balancer, Google Cloud's Network Load Balancer). Health checks, redundancy and scaling are covered in High Availability, Scaling and Disaster Recovery.
With the AWS Command Line Interface (CLI) you can list the subnets of a VPC and check which ones hand out public addresses. The command is shown with sample output; it needs an AWS account and credentials:
$ aws ec2 describe-subnets --filters Name=vpc-id,Values=vpc-0a1b2c3d4e5f67890 \
--query "Subnets[].[AvailabilityZone,CidrBlock,MapPublicIpOnLaunch]" --output table
------------------------------------------- | DescribeSubnets | +-------------+----------------+----------+ | eu-west-1a | 10.0.0.0/20 | True | | eu-west-1b | 10.0.16.0/20 | True | | eu-west-1a | 10.0.32.0/20 | False | | eu-west-1b | 10.0.48.0/20 | False | +-------------+----------------+----------+
Common mistakes
-
Overlapping ranges. Every VPC created with the
console's default range (such as
10.0.0.0/16or172.31.0.0/16) looks the same, and the day you need to connect two of them, you cannot. Plan ranges centrally. - Databases in public subnets. A database never needs a public IP address. Put it in a private subnet and allow its port only from the application's security group.
- One NAT gateway for all zones. It becomes a single point of failure and adds cross-zone data charges.
- Forgetting return traffic in a network ACL. Because network ACLs are stateless, allowing inbound port 443 without allowing outbound ephemeral ports breaks every connection.
- Subnets that are too small. A /28 has 16 addresses, of which 11 are usable in AWS; autoscaling groups and containers run out fast.