Cloud Networking in Production · Address Planning
The Acquisition That Couldn't Connect
Two networks with the same private range can't be peered or routed. Plan ranges centrally, and know the workarounds when you inherit an overlap.
Scenario
Example company: an online retailer runs production in an AWS VPC 10.0.0.0/16, the default range in many tutorials and templates. It acquires a smaller company whose AWS VPC is also 10.0.0.0/16, and the integration plan says "peer the VPCs in week one".
What you see in production
The peering request is rejected: AWS doesn't allow peering between VPCs with overlapping CIDR blocks. The team tries a Transit Gateway instead; the attachments succeed, but routes to 10.0.0.0/16 point back at each VPC's own local route, so nothing crosses. The office VPN (10.0.8.0/22) also overlaps. Months later, the integration still runs over public endpoints.
The naive code
Every team picks its range independently, usually the template default:
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16" # the default in countless examples
}
Reproduce it
An address plan is just a set of ranges; overlaps are easy to detect and easy to avoid if one place allocates them:
import ipaddress
existing = {
"retail-prod": "10.0.0.0/16",
"acquired-prod": "10.0.0.0/16",
"office-vpn": "10.0.8.0/22",
"retail-dev": "10.1.0.0/16",
}
nets = {k: ipaddress.ip_network(v) for k, v in existing.items()}
names = list(nets)
for i, a in enumerate(names):
for b in names[i + 1:]:
if nets[a].overlaps(nets[b]):
print(f"overlap: {a} {nets[a]} <-> {b} {nets[b]}")
# The fix going forward: one pool, allocations that can never collide.
pool = ipaddress.ip_network("10.64.0.0/10")
used = [n for n in nets.values() if n.overlaps(pool)]
free = (s for s in pool.subnets(new_prefix=16) if not any(s.overlaps(u) for u in used))
for env in ["payments-prod", "payments-staging", "data-prod"]:
print(f"allocate {env:17} -> {next(free)}")
Output
overlap: retail-prod 10.0.0.0/16 <-> acquired-prod 10.0.0.0/16 overlap: retail-prod 10.0.0.0/16 <-> office-vpn 10.0.8.0/22 overlap: acquired-prod 10.0.0.0/16 <-> office-vpn 10.0.8.0/22 allocate payments-prod -> 10.64.0.0/16 allocate payments-staging -> 10.65.0.0/16 allocate data-prod -> 10.66.0.0/16
Root cause
Routing works by destination address. When two connected networks contain the same address, a router can't know which side 10.0.4.20 refers to, so cloud providers refuse peering and ignore routes that overlap a VPC's own local range. Overlaps are created on day one, by default values and the absence of a central allocation, and only discovered when networks need to connect.
The fix
Going forward: allocate every range from a central plan. Reserve a large block per cloud and region, carve /16s (or /20s) per environment and account, and record every allocation, including offices, VPN client pools and partners.
resource "aws_vpc_ipam_pool" "prod_eu" {
address_family = "ipv4"
ipam_scope_id = aws_vpc_ipam.main.private_default_scope_id
locale = "eu-west-1"
}
resource "aws_vpc" "payments" {
ipv4_ipam_pool_id = aws_vpc_ipam_pool.prod_eu.id
ipv4_netmask_length = 16 # IPAM hands out a non-overlapping /16
}
Azure: Azure Virtual Network Manager IP address management pools, or a central plan in code.
GCP: plan per Shared VPC host project; subnets are created centrally, so service projects can't pick ranges.
When you inherit an overlap:
- Expose services, not networks: AWS PrivateLink (Azure Private Link, GCP Private Service Connect) publishes a service behind one IP in the consumer's network, and overlap doesn't matter.
- Translate addresses: a private NAT gateway (or NAT on a firewall appliance) hides one side behind a non-overlapping range.
- Re-IP the smaller side: add a new non-overlapping CIDR, move workloads to new subnets, remove the old range. Slow, but the only clean long-term answer.
Trade-offs
- Large reservations (a
/16per environment) waste addresses on paper, but private space is plentiful; running out or overlapping is far more expensive. - PrivateLink and NAT work around overlaps one service at a time and add cost and hops; they don't give full network connectivity.
- Central IPAM adds a request step for teams; automate it through Terraform modules so it isn't a ticket queue.
Detect it in production
- CI check: every new VPC/VNet range must come from the IPAM pool (deny literal
cidr_blockvalues with policy). - Report overlaps across all accounts, offices and VPN pools, not just within one account.
- Watch for rejected peering requests and routes marked as blackholes or not propagated.
Key points
- Overlapping ranges can't be routed to each other, which blocks peering, transit and VPN.
- Allocate every range from one central plan (VPC IPAM, a shared module or a spreadsheet), including non-cloud networks.
- Inherited overlap: expose services via PrivateLink, translate with NAT, or re-IP.
Practice questions
Why can't two VPCs with overlapping CIDR blocks be peered?
Answer
Routing is by destination address. If both sides contain the same range, the route to the peer would conflict with each VPC's own local route, so the provider rejects the peering.
How do you connect a service in a network that overlaps yours without re-addressing?
Answer
Publish it through PrivateLink (Azure Private Link, GCP Private Service Connect), which places one endpoint IP in your network, or translate addresses with a private NAT gateway. Both work per service, not as full connectivity.
What does a central IP address manager give you?
Answer
Pools that hand out non-overlapping ranges automatically, a record of every allocation, and policies (for example, minimum and maximum sizes per pool), so overlaps can't be created by accident.
Open in the app to mark lessons complete, take notes and track your progress.