🌐 Cloud Networking Essentials: Addresses, Subnets, Routes and Rules
The seven ideas behind every cloud network (address space, subnets, routes, gateways, firewall rules, DNS, connections), with the address and routing maths run for real, and what Azure, AWS, Google Cloud and Oracle Cloud call each of them
💡 How this page was made. The maths on this page was run for real: the address calculations, the subnet plan, the route lookups and the firewall-rule demo are Python programs, run on 6 October 2026 with the ipaddress module that comes with Python (nothing was downloaded). The firewall and route demos are small models I wrote to show the idea, not the clouds' real engines. The facts about each cloud (reserved addresses, defaults, limits) are from the providers' documentation, read on 6 October 2026, and nothing was run on a cloud account. The same ideas appear in the services maps and in the FreeBSD Corner's pf firewall, WireGuard and NFS pages.
Seven ideas
A cloud network is a software copy of an ordinary network. Learn these seven ideas once and the four clouds are one subject with four vocabularies:
- An address space: the block of private addresses that is yours (written in CIDR, like 10.0.0.0/16).
- Subnets: slices of that block, where things are placed.
- Routes: the table that says where each destination's traffic goes next.
- Gateways: the doors out: to the internet, to the internet but outbound only (NAT), to another network.
- Firewall rules: what traffic is allowed in and out.
- Names: DNS, inside and outside.
- Connections: peering, VPNs and private links between networks.
What each cloud calls the first one:
- Azure: a virtual network (VNet). It is limited to one region, but it spans the availability zones of that region.
- AWS: a VPC (Virtual Private Cloud), in one region.
- Google Cloud: a VPC network, which is global. Its subnets are regional.
- Oracle Cloud: a VCN (Virtual Cloud Network), in one region.
Addresses: CIDR in five minutes
A network address is written as an address plus a slash and a number, such as 10.0.0.0/16. The number says how many bits are fixed (the "network part"). The rest is free for hosts. A bigger number means a smaller network: /16 is 65,536 addresses, /24 is 256, /28 is 16. Real output from Python:
>>> import ipaddress
>>> vpc = ipaddress.ip_network('10.0.0.0/16')
>>> vpc.num_addresses
65536
>>> vpc.netmask
IPv4Address('255.255.0.0')
>>> vpc.is_private
True
>>> subnets = list(vpc.subnets(new_prefix=24))
>>> len(subnets)
256
>>> [str(s) for s in subnets[:3]]
['10.0.0.0/24', '10.0.1.0/24', '10.0.2.0/24']
>>> str(subnets[-1])
'10.0.255.0/24'
>>> ipaddress.ip_network('10.0.1.0/24').num_addresses
256
>>> ipaddress.ip_address('10.0.1.77') in ipaddress.ip_network('10.0.1.0/24')
True
>>> ipaddress.ip_address('10.0.2.77') in ipaddress.ip_network('10.0.1.0/24')
False
>>> ipaddress.ip_network('8.8.8.0/24').is_private
False
>>> ipaddress.ip_network('100.64.0.0/10').is_private
False
- A /16 has 65,536 addresses and the netmask 255.255.0.0. Cut into /24 pieces it makes 256 subnets, from 10.0.0.0/24 to 10.0.255.0/24.
- Membership is a calculation: 10.0.1.77 is inside 10.0.1.0/24, and 10.0.2.77 is not.
- Private ranges (RFC 1918) are 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16: addresses that are never routed on the public internet, and the ones to use for a cloud network. Python says 10.0.0.0/16 is private and 8.8.8.0/24 is not.
- A trap in the last line. Python reports 100.64.0.0/10 (the "shared address space", RFC 6598) as not private, but the clouds accept it: Google Cloud lists it among the valid subnet ranges, and Azure's documentation says it treats it as a private space. Do not take a language library's idea of "private" as the cloud's.
The reserved addresses
Every cloud keeps a few addresses in each subnet for itself. From the documentation (read on 6 October 2026):
- AWS and Azure: 5. The first four and the last. In 10.0.0.0/24: .0 the network, .1 the router, .2 DNS, .3 reserved for the future, and .255 the broadcast.
- Google Cloud: 4 (in a primary range): the first (network), the second (the default gateway), the second-to-last (reserved) and the last (broadcast). Secondary ranges reserve none.
- Oracle Cloud: 3: the first (network), the last (broadcast) and the first host address (the gateway).
So the usable size is smaller than the block. The table shows how fast that bites for small subnets (computed, not copied):
>>> import ipaddress
>>> for p in (16, 24, 28, 29, 30):
... n = ipaddress.ip_network('10.0.0.0/%d' % p)
... t = n.num_addresses
... print('/%d total %6d AWS/Azure usable %6d GCP usable %6d OCI usable %6d' % (p, t, t-5, t-4, t-3))
...
/16 total 65536 AWS/Azure usable 65531 GCP usable 65532 OCI usable 65533
/24 total 256 AWS/Azure usable 251 GCP usable 252 OCI usable 253
/28 total 16 AWS/Azure usable 11 GCP usable 12 OCI usable 13
/29 total 8 AWS/Azure usable 3 GCP usable 4 OCI usable 5
/30 total 4 AWS/Azure usable -1 GCP usable 0 OCI usable 1
- A /24 always gives 251 to 253 usable addresses. A /28 gives 11 to 13, a /29 gives 3 to 5.
- The /30 line shows a negative number for AWS and Azure: 4 addresses minus 5 reserved is nonsense, which is exactly why those clouds do not allow it. The documented limits: an AWS subnet is between /28 and /16, an Azure subnet between /29 and /2, and an Oracle VCN between /16 and /30.
- Plan for the reserved addresses: services that place many network cards in a subnet (managed Kubernetes, scale sets) can use hundreds of addresses, so a /28 is a trap.
Overlaps: the number-one planning mistake
Two networks that overlap cannot be connected sensibly: routes become ambiguous. AWS's documentation says the subnets of one VPC cannot overlap each other, and the same logic applies between networks you peer, join by VPN or reach from your office. Check with a calculation, not by eye:
>>> import ipaddress
>>> a = ipaddress.ip_network('10.0.0.0/16')
>>> b = ipaddress.ip_network('10.0.5.0/24')
>>> c = ipaddress.ip_network('10.1.0.0/16')
>>> a.overlaps(b)
True
>>> a.overlaps(c)
False
>>> office = ipaddress.ip_network('192.168.0.0/16')
>>> [(str(n), n.overlaps(office)) for n in (a, c, ipaddress.ip_network('192.168.10.0/24'))]
[('10.0.0.0/16', False), ('10.1.0.0/16', False), ('192.168.10.0/24', True)]
>>> list(ipaddress.collapse_addresses([ipaddress.ip_network('10.0.0.0/24'), ipaddress.ip_network('10.0.1.0/24')]))
[IPv4Network('10.0.0.0/23')]
>>> str(ipaddress.ip_network('10.0.1.0/25').supernet(new_prefix=16))
'10.0.0.0/16'
- 10.0.0.0/16 contains 10.0.5.0/24 (an overlap), but does not overlap 10.1.0.0/16.
- Against an office network of 192.168.0.0/16, the two 10.x ranges are fine and 192.168.10.0/24 is not. Home and office networks very often use 192.168.x.x or 10.0.x.x, so a cloud network on the same range cannot be joined to them.
- collapse_addresses merged two neighbouring /24s into one /23, and supernet found the /16 above a /25: the tools for tidying a plan.
The habit: choose address space before you create anything, write the plan down, and give each network, region and environment its own non-overlapping block. You can often add ranges later, but you cannot easily change a range that is in use (Google Cloud, for example, lets you expand a subnet's primary range, but not replace or shrink it).
Subnets
A subnet is a slice of the address space where resources are placed. The scope differs between clouds, and it matters for resilience:
- AWS: a subnet lives in one Availability Zone. To be zone-resilient you make a subnet per zone.
- Azure: a subnet is part of a VNet, and the VNet spans the zones of its region.
- Google Cloud: subnets are regional resources (the network is global).
- Oracle Cloud: subnets can be regional (Oracle recommends this: more flexible, easier for disaster recovery) or tied to one availability domain (the older style).
Public and private
The words mean "can this thing be reached from the internet, and can it reach out?" The details are Oracle-specific and AWS-specific:
- AWS: by the documentation, a subnet is public when its route table sends traffic to an internet gateway, and the instances in it have public addresses. The default VPC (made for you in every region, for accounts created after December 4, 2013) has a default subnet in each zone, an internet gateway and a route that sends everything to it, so an instance in a default subnet can reach the internet at once. In a non-default subnet, instances have only private addresses unless you add a public address.
- Oracle Cloud: a subnet is public or private as an attribute. A private subnet does not allow public IPv4 addresses. For a thing to reach the internet, Oracle lists five conditions together: an internet gateway, a public subnet, a route rule to the gateway, a security rule that allows the traffic, and a public IP on the instance.
- Azure and Google Cloud have no "public subnet" setting: what makes something reachable is a public IP address plus the rules and routes that allow it.
A good default design: public subnets for things that must be reached (load balancers), private subnets for everything else (application servers, databases), and a separate subnet for databases.
>>> import ipaddress
>>> vpc = ipaddress.ip_network('10.0.0.0/16')
>>> blocks = list(vpc.subnets(new_prefix=24))
>>> plan = {'public-a': blocks[0], 'public-b': blocks[1], 'private-a': blocks[10], 'private-b': blocks[11], 'database-a': blocks[20], 'database-b': blocks[21]}
>>> for name, net in plan.items():
... print('%-11s %-14s %3d usable (AWS/Azure)' % (name, net, net.num_addresses - 5))
...
public-a 10.0.0.0/24 251 usable (AWS/Azure)
public-b 10.0.1.0/24 251 usable (AWS/Azure)
private-a 10.0.10.0/24 251 usable (AWS/Azure)
private-b 10.0.11.0/24 251 usable (AWS/Azure)
database-a 10.0.20.0/24 251 usable (AWS/Azure)
database-b 10.0.21.0/24 251 usable (AWS/Azure)
>>> first = ipaddress.ip_network('10.0.0.0/24')
>>> h = list(first)
>>> print('network', h[0], '| router/gateway', h[1], '| DNS', h[2], '| reserved', h[3], '| first usable', h[4], '| broadcast', h[-1])
network 10.0.0.0 | router/gateway 10.0.0.1 | DNS 10.0.0.2 | reserved 10.0.0.3 | first usable 10.0.0.4 | broadcast 10.0.0.255
Here is a plan for a 10.0.0.0/16 network across two zones: two public, two private and two database subnets, each a /24 with 251 usable addresses (on AWS or Azure). The numbers are spaced out (0, 10, 20) so that there is room to grow and each tier is easy to recognize. The last two lines show the reserved addresses of the first subnet, as AWS documents them: the router at .1, DNS at .2 (the VPC base plus two), .3 reserved, and the first usable address at .4.
Routes
A route table says where traffic for each destination goes. Each rule has a destination range and a target (a gateway, a peering connection, a VPN, a network card). When several rules match, the most specific one wins (the longest prefix). A small model, with typical routes:
>>> import ipaddress
>>> routes = [('10.0.0.0/16', 'local'), ('10.0.10.0/24', 'peering-to-dev'), ('192.168.0.0/16', 'vpn-to-office'), ('0.0.0.0/0', 'internet-gateway')]
>>> table = [(ipaddress.ip_network(n), t) for n, t in routes]
>>> def lookup(ip):
... ip = ipaddress.ip_address(ip)
... best = max((r for r in table if ip in r[0]), key=lambda r: r[0].prefixlen, default=None)
... if best is None: return '%s -> no route: the packet is dropped' % ip
... return '%s -> %s (matched %s)' % (ip, best[1], best[0])
...
>>> for dest in ('10.0.3.9', '10.0.10.4', '192.168.4.20', '93.184.216.34'):
... print(lookup(dest))
...
10.0.3.9 -> local (matched 10.0.0.0/16)
10.0.10.4 -> peering-to-dev (matched 10.0.10.0/24)
192.168.4.20 -> vpn-to-office (matched 192.168.0.0/16)
93.184.216.34 -> internet-gateway (matched 0.0.0.0/0)
>>> table = [r for r in table if r[1] != 'internet-gateway']
>>> print(lookup('93.184.216.34'))
93.184.216.34 -> no route: the packet is dropped
- Traffic inside the network (10.0.0.0/16) stays local.
- 10.0.10.0/24 has its own, more specific route (here, a peering connection to another network): it beats the broader /16 rule.
- 192.168.0.0/16 goes over a VPN to the office.
- Everything else is 0.0.0.0/0, the default route, here the internet gateway.
- Without a default route, there is no way out: after removing it, the last lookup printed "no route: the packet is dropped". This is what the cloud does with a packet that has nowhere to go, and it is the first thing to check when a machine "cannot reach the internet".
How the clouds do it:
- AWS: route tables, associated with subnets. A subnet without an explicit table uses the main route table. Targets include the internet gateway, a NAT gateway, a peering connection, a transit gateway or a VPN.
- Oracle Cloud: each subnet uses one route table. A VCN starts with an empty default table. Targets are gateways or a compute instance.
- Azure and Google Cloud: there are system routes created for you (the network itself, the internet), and user-defined routes (Azure) or custom static routes (Google Cloud) that you add (from the documentation, not read in detail for this page).
Gateways
- Internet gateway. Two-way internet access for resources with public addresses.
- NAT gateway (or NAT device). Outbound-only internet access for things without public addresses: they can download updates, and unsolicited connections from the internet cannot reach them. AWS and Oracle document it that way. It is a very common building block, and it bills by the hour plus data (see the services maps).
- Service gateway (Oracle) and similar private endpoints elsewhere: private access to the cloud's own services (object storage, databases) without using the internet.
- Dynamic routing gateway (Oracle) / virtual private gateway (AWS) / VPN gateway (Azure): the door for VPNs and dedicated links.
Firewall rules
Every cloud filters traffic with rules. Three words decide how they behave:
- Stateful: if a connection is allowed in one direction, the replies are allowed automatically. All the main cloud firewalls are stateful (like pf with keep state).
- Priority: some firewalls (Azure, Google Cloud) number their rules and the first match wins; others (AWS security groups) just add up their allow rules.
- Default: what happens when nothing matches.
What the documentation says for each:
- Azure network security groups (NSG). Rules have a priority from 100 to 4096 (lower is processed first, and processing stops at the first match), a direction, a source, a destination, a protocol, a port range and an action (allow or deny). They are stateful: removing the rule that allowed a connection does not cut the connections already open, only new ones. Default rules are always there, at priorities 65000 and up: in, allow from the VirtualNetwork (65000), allow from the Azure load balancer (65001), deny everything else (65500); out, allow to the VirtualNetwork (65000), allow to the internet (65001), deny the rest (65500). You cannot delete the defaults, but a rule with a lower number overrides them. An NSG is attached to a subnet or a network card.
- AWS security groups. Attached to instances, stateful, with allow rules only: by default nothing comes in and everything goes out. Network ACLs are a second, stateless layer on the subnet. (I did not re-read AWS's security group pages for this page: these are the standard behaviours, also described on the AWS services map.)
- Google Cloud VPC firewall rules. Stateful, with a priority from 0 to 65,535 (default 1000, lower wins), each rule allow or deny. Two implied rules always exist: allow all egress and deny all ingress. A rule applies to all instances in the network, or only to those with a network tag or a service account (not both in one rule). The default network comes with four ingress rules: SSH, RDP, ICMP and "internal" (allow between its own instances).
- Oracle Cloud. Security lists apply to every resource in a subnet that uses the list. Network security groups apply only to the resources you put in the group, which is finer. Each rule is either stateful (replies automatic) or stateless (you must write both directions). And the instance's own firewall is a second layer (the services map page and the free VM guide explain this "two firewalls" problem).
A model of priority, first match wins and defaults, in the Azure style (my own demo, with rules like the ones above):
>>> rules = [(100, 'Allow', 'tcp', 22, '203.0.113.0/24'), (200, 'Deny', 'tcp', 22, '0.0.0.0/0'), (300, 'Allow', 'tcp', 443, '0.0.0.0/0'), (65000, 'Allow', 'any', None, 'VirtualNetwork'), (65500, 'Deny', 'any', None, '0.0.0.0/0')]
>>> import ipaddress
>>> def decide(proto, port, src):
... ip = ipaddress.ip_address(src)
... for prio, act, p, prt, source in sorted(rules):
... if p not in ('any', proto): continue
... if prt not in (None, port): continue
... if source == 'VirtualNetwork': ok = ip in ipaddress.ip_network('10.0.0.0/16')
... else: ok = ip in ipaddress.ip_network(source)
... if ok: return '%s by rule %d' % (act, prio)
...
>>> decide('tcp', 22, '203.0.113.9')
'Allow by rule 100'
>>> decide('tcp', 22, '198.51.100.7')
'Deny by rule 200'
>>> decide('tcp', 443, '198.51.100.7')
'Allow by rule 300'
>>> decide('tcp', 3306, '10.0.4.4')
'Allow by rule 65000'
>>> decide('tcp', 3306, '198.51.100.7')
'Deny by rule 65500'
- Rule 100 allows SSH (port 22) only from one office range (203.0.113.0/24). Rule 200 denies SSH from everyone else. Rule 300 allows HTTPS from everywhere.
- SSH from the office: allowed by 100. SSH from anywhere else: denied by 200. HTTPS from anywhere: allowed by 300.
- Port 3306 (a database) from inside the network is allowed by the default rule 65000, and from outside is denied by the default 65500. Nobody wrote a rule for the database: the defaults decided. That is the point of "deny by default".
- Order matters: swap the numbers of rules 100 and 200 and the office would be locked out.
Rules of thumb that hold on every cloud:
- Open the smallest thing for the fewest sources. SSH from your own address, not from 0.0.0.0/0.
- Name the source. Prefer "this group" or "this subnet" to a list of addresses.
- Databases never get a public rule. They accept traffic from the application tier only.
- Write rules down. A firewall that nobody can explain is a risk. (Compare the habits on the pf page.)
Names: DNS
- Every cloud network provides an internal DNS so that machines find each other by name. On AWS the DNS server is at the base of the VPC range plus two (which is why .2 of each subnet is reserved). On Azure, addresses .2 and .3 of each subnet are reserved to map the Azure DNS addresses into the network, and the platform uses the special virtual address 168.63.129.16 for services such as DNS and health checks.
- Public names come from the DNS services (Azure DNS, Route 53, Cloud DNS, Oracle DNS), and private zones serve names that only exist inside your networks.
- When DNS "does not work" in a new network, check which resolver the machine uses, and whether the private zone is linked to the network.
Connecting networks
- Peering. A private connection between two networks, so that machines talk by private address "as if in the same network" (AWS and Azure documentation). Azure peering works across subscriptions and across regions (global peering). Oracle uses local peering (same region) and remote peering (other regions) through a dynamic routing gateway. Peering needs non-overlapping ranges, which is why the planning above matters. Peering is usually not transitive: A to B and B to C does not give A to C.
- VPN. An encrypted tunnel over the internet to your office or another cloud: AWS Site-to-Site VPN (two tunnels), Azure VPN Gateway, Google Cloud VPN, Oracle Site-to-Site VPN. The same idea as the real WireGuard tunnel on the WireGuard page, with the cloud running one end for you.
- Transit hubs. To join many networks without peering each pair: AWS Transit Gateway (a regional virtual router), Oracle DRG in a hub-and-spoke design, and the equivalents on the other clouds.
- Dedicated links. A private line instead of the internet: AWS Direct Connect, Azure ExpressRoute, Google Interconnect, Oracle FastConnect. (Named here, not covered.)
Load balancers, in one paragraph
A load balancer spreads incoming traffic over several machines and checks which are healthy. Each cloud has a layer 7 one that understands web requests (AWS Application Load Balancer, Azure Application Gateway, Google Cloud Load Balancing, Oracle Load Balancer) and a layer 4 one for plain TCP and UDP (AWS Network Load Balancer, Azure Load Balancer, Oracle Network Load Balancer). Put them in the public subnets and the machines behind them in private ones. They bill for existing, so count them in your costs.
What costs money in a network
- NAT gateways, load balancers, VPN gateways and public IP addresses bill by the hour while they exist.
- Data leaving the cloud, and data between regions, is billed. Data between zones in one region differs by cloud: check the pricing page of yours.
- Peering and transit have their own data charges on some clouds.
- The free tiers include very little networking. The services maps and the free VM guides list the traps.
Common mistakes
- Overlapping address ranges between a cloud network and the office (or another cloud).
- Too small a subnet, forgetting the reserved addresses and what managed services consume.
- No default route (or a NAT gateway missing), so a "private" machine cannot even download updates.
- A public rule for a database.
- 0.0.0.0/0 on SSH or RDP.
- Forgetting the second firewall: on Oracle Cloud and in any machine with its own firewall, both layers must allow the traffic.
- Debugging by guessing. Follow the packet: address, route, gateway, firewall rule (both directions of the rule set), the machine's own firewall, the application.
What this page did not cover
IPv6, private endpoints and service endpoints, transit routing and hybrid DNS, egress filtering and firewall appliances, and network observability (flow logs). Each cloud's networking documentation covers these in depth.
The cheat sheet
- The seven ideas: address space, subnets, routes, gateways, firewall rules, DNS, connections.
- Names: VNet (Azure, one region, spans zones) | VPC (AWS, one region) | VPC network (Google, global, regional subnets) | VCN (Oracle, one region).
- CIDR: /16 = 65,536, /24 = 256, /28 = 16 addresses. Bigger number, smaller network. Private: 10/8, 172.16/12, 192.168/16 (the clouds also accept 100.64/10).
- Reserved per subnet: AWS 5, Azure 5, Google 4, Oracle 3. Allowed sizes: AWS subnet /28 to /16, Azure subnet /29 to /2, Oracle VCN /16 to /30.
- Plan first: non-overlapping ranges per network, region and environment. Public, private and database subnets per zone. Spacing for growth.
- Routes: longest prefix wins. 0.0.0.0/0 is the way out. No default route, no internet. NAT for outbound-only.
- Firewalls: stateful everywhere. Azure NSG priority 100 to 4096 (defaults 65000 and up), Google 0 to 65,535 (implied allow egress, deny ingress), AWS security groups allow-only, Oracle security lists or NSGs (stateful or stateless rules).
- Connect: peering (non-overlapping, not transitive), VPN, transit hub, dedicated link.
- Debug by following the packet: address, route, gateway, cloud firewall, the machine's own firewall, the application.
← Terraform Basics: Describe It, Plan It, Apply It · ↑ All tutorials · Cloud IAM Essentials: Who Can Do What, and Where →