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.

A VPC with range 10.0.0.0/16 across two availability zones. An internet gateway connects the VPC to the internet. Each zone has a public subnet with a load balancer node and a NAT gateway, and a private subnet with an application server and a database. The application servers send outbound traffic through the NAT gateway in their own zone. Internet Internet gateway VPC 10.0.0.0/16 Availability zone a Availability zone b Public subnet 10.0.0.0/20 NAT gateway Load balancer 0.0.0.0/0 → internet gw Private subnet 10.0.32.0/20 App server DB primary 0.0.0.0/0 → NAT gw outbound Public subnet 10.0.16.0/20 NAT gateway Load balancer 0.0.0.0/0 → internet gw Private subnet 10.0.48.0/20 App server DB standby 0.0.0.0/0 → NAT gw outbound
One VPC across two availability zones. The public subnets route internet traffic to the internet gateway; the private subnets send it to the NAT gateway in their own zone, so they can make outbound connections but cannot be reached from outside.

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.

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/16 or 172.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.