Plan Your VPC Usage

AWS is complex. Sign up for free, useful lessons like this.

A VPC is a "Virtual Private Cloud", which is just a private network. You create a private network so that your servers (and other stuff) within that network can communicate with each other securely.

Stuff residing within different VPCs can't talk to each other, unless you want them to. Herein lie the caveats and foot guns that are the topic of this article.

The Default VPC

Your account comes with a default VPC so that you can create resources (servers and stuff) without the extra friction of having to think about networking.

This is convenient, but there's a few reasons why not to use the default VPC:

  1. It's not using the IP address ranges you likely want to use
  2. It only sets up "public subnets", and you probably want private ones

You can adjust the default VPC (or even delete it), but there are better options.

Keeping Your Options Open

The tl;dr of caring about your VPC boils down to optionality. If you aren't doing any planning, you may run into some foot-guns. Here's what you we want to avoid:

  1. Restricting our ability to peer VPC's together
  2. Having no idea what IP address ranges are available to use

You may think "I'll never need VPC peering!". That's possible! But for "serious" AWS usage (wherein "serious" is something along the lines of "we pay staff to manage cloud stuff"), peering often becomes convenient or even necessary.

Point 2 follows from point 1, so if you're in the "I don't use AWS like this" camp, you can throw this article into your bookmarks (/dev/null) for later.

VPC Peering

VPC's are often used as a way to segment parts of your "stuff", for example development vs production workloads, or, say, the finance bean-counters step functions vs the application bean-spenders servers.

Here's the deal tho - some day, finance is going to want to query your application database. But they can't! Their stuff lives in another VPC!

This solution to this is VPC Peering, which allows you to connect those private networks. When used, resources within the peered VPC's can talk to each other over the private network.

Enter foot-gun, stage right: If your VPC's are using the same IP address range, they can't be peered together. Imagine some code querying a database at 10.1.12.34 when that IP address exists in both VPCs. Pandemonium!

Since we can't predict the future, and future is actively trying to screw us, we want to plan ahead!

The objective: Don't create VPCs with overlapping IP addresses without a damn good reason.

Tracking IP Usage

If you make a lot of VPC's for different business units (or whatever), it's pretty easy to lose track of which VPC ranges are available.

This is especially true if you're not consistent with your VPC sizes. For example, you might create a VPC of size 10.0.0.0/16 (~56k IP addresses), and then later decide the finance team only needs a few IP's and create something like 10.1.0.0/24 (256 IPs).

Inconsistently sized VPCs make it very hard to track what IP address ranges are still available for use, and makes it less efficient, as you might leave "gaps" of unused addresses. Gaps are both hard to find, and may not be easily used later.

Having a plan and a way to track IP address usage ahead of time means you'll be able to avoid painful issues later.

So, lets see what our options are.

Deciding on IP Addresses

There are 3 IP ranges we can use in our VPCs (Docs here). The tl;dr is that these are available to you:

Total CIDR IP Range Total IPs
10.0.0.0/8 10.0.0.0 - 10.255.255.255 16,777,216
172.16.0.0/12† 172.16.0.0 - 172.31.255.255 1,048,576
192.168.0.0/16†† 192.168.0.0 - 192.168.255.255 65,536

†AWS doesn't want you to use 172.17.0.0/16 because some internal services use it. This forbidden range is, annoyingly, in the middle of the 172.16.0.0/12 block. They won't stop you from using it though, so good luck with that, hope you randomly read that section of the docs ✌️.

††The largest IPv4 size you can create of any one VPC is /16, which is 65,536 IPs (docs). That means 192.168.0.0/16 is kinda lame - it can be used for one VPC or maybe special cases where you want to create smaller VPCs.

The Verdict:

Use the 10.0.0.0/8 range as your "go to" for VPC creation. It gives us the most optionality - there's a LOT of IP addresses, and there's no caveats.

Carving Out VPCs

So, knowing all of that, we want to take that large range 10.0.0.0/8 and decide how to carve it out. The goal is to be able to create as many VPCs as possible, and decide how to divide them up for our "business model" (or whatever).

This likely means segmenting environments and perhaps business units (looking at you, Finance).

But first, Math

Luckily, all we need to do is divide by 2, and we don't really need to understand IP addresses.

Here's the bare minimum you need to know - it'll get you pretty far.

Facts:

  • When using CIDR notation, there's an IP address, and a / denoting a "prefix length":

<ip-address>/<prefix-length, e.g. 10.0.0.0/8.

  • The smaller the prefix length (/8), the larger the IP address range it denotes.
  • Every time you add 1 to the prefix length, the IP address range is cut in half
CIDR Range Number of IPs
10.0.0.0/8 10.0.0.0 - 10.255.255.255 16,777,216
10.0.0.0/9 10.0.0.0 - 10.127.255.255 8,388,608
10.0.0.0/10 10.0.0.0 - 10.63.255.255 4,194,304
10.0.0.0/11 10.0.0.0 - 10.31.255.255 2,097,152
10.0.0.0/12 10.0.0.0 - 10.15.255.255 1,048,576
10.0.0.0/13 10.0.0.0 - 10.7.255.255 524,288
10.0.0.0/14 10.0.0.0 - 10.3.255.255 262,144
10.0.0.0/15 10.0.0.0 - 10.1.255.255 131,072
10.0.0.0/16 10.0.0.0 - 10.0.255.255 65,536

So, great - the math isn't too bad…except…

We don't know yet how to make contiguous blocks of IP addresses. For example, 10.0.0.0/9 is half of 10.0.0.0/8, so we should get 2 IP address ranges out of that. What are the 2 (contiguous) halves?

Rather than do math, let's use some tools. My go-to is actually Terraform. It has a function cidrsubnets that you can use to calculate contiguous IP address blocks (subnets). It works like this:

1#> terraform console
2cidrsubnets("10.0.0.0/8", 1, 1)
3 
4tolist([
5 "10.0.0.0/9",
6 "10.128.0.0/9",
7])

We got 2 /9 CIDR blocks there - each is one half of 10.0.0.0/8.

  • First half: 10.0.0.0 - 10.127.255.255
  • Second half: 10.128.0.0 - 10.255.255.255

But what did we do there? The arguments to the function are:

  1. The CIDR you're splitting up into subnets
  2. An arbitrary set of numbers. For each number, you'll get a new subnet. The value of the number is how many "bits" you add to the base CIDR prefix - so if we started with /8, and foreach subnet we want, we add 1 bit to get subnets of /9.

To firm that up, let's see another example - let's divide a /9 block into /16 subnets:

1#> terraform console
2cidrsubnets("10.0.0.0/9", 7, 7, 7, 7, 7, 7, 7, 7)
3 
4tolist([
5 "10.0.0.0/16",
6 "10.1.0.0/16",
7 "10.2.0.0/16",
8 "10.3.0.0/16",
9 "10.4.0.0/16",
10 "10.5.0.0/16",
11 "10.6.0.0/16",
12 "10.7.0.0/16",
13])

To get subnets of size /16, we take the base bit /9 and add 7 (9+7=16). We told cidrsubnets it to calculate 8 contiguous /16 subnets by passing it eight arguments of value 7.

We could create 128 subnets of size /16 out of this /9 range! If we tried to create 129 subnets, we'd get an error (since that's out of range of the base CIDR 10.0.0.0/9).

Typing in 7 128 times sounds exhausting. Luckily it's easy to see the pattern there - the last subnet our example would allow is 10.127.0.0/16. That's not to say that 10.128.0.0/16 doesn't exist - it does! It's just not within the 10.0.0.0/9 range. Instead it's within the 10.128.0.0/9 CIDR range.

So, we are gonna do math?

Not really - we just need to know enough to divide a CIDR a few times and try to cram in as many /16 CIDR's as possible. Why /16? Well, remember, the largest IPv4 VPC size you can create is /16, which is 65,536 IPs (docs).

An Example VPC Plan

Planning out your VPC usage does not need to be hard - we'll make a simple plan.

First, we'll say that we may operate out of 2 regions - us-east-2 and eu-central-1.

Then we'll say that within each region, we want to support three environments - dev, staging, and production. To keep things simpler, we'll leave poor Finance alone and pretend we don't care about other business units.

Here's what an IP address breakdown might look like.

a vpc plan

We start with the most IP addresses we can get (10.0.0.0/8) and devise a strategy that lets us plan out how we want to get to a bunch of /16 subnets (each of which will be a VPC).

We have 2 regions we might operate in. CIDR blocks conveniently split themselves into two by just adding a bit to the prefix, so we'll use a /9 per region. Then we can divide each region into our 3 environments.

Having 3 environments is awkward in terms of IP addressing (continuously dividing by 2 is easiest!). What I did here is divide each /9 into 4 blocks and allocated 2 of them to production. In my case, Production was the most likely environment to need more VPCs.

So, we have a plan! We have plenty of VPCs to play with (for most use cases) and so we can utilize them knowing that we can peer any VPC's together across any environment. If all else fails, we also have the "escape hatch" of the other 2 private network IP ranges.

Lastly, we have the neat ability to look at any given IP address and say "Oh, that's in us-east-2 in the dev environment".

IPAM

You can take this a step farther and use AWS's IPAM service to plan our your VPC's. You create "pools" of IP address blocks, and when you create a VPC, you can allocate it from one of these pools.

This helps you track what VPC's are "compliant" with your rules and what CIDR's are (and are not) available to use.

The service works across multi-account AWS organizations as well! This video does a decent job of showing it off.

An Individual VPC

There's a course here on VPC Basics already, but it doesn't go into how you may want to generate subnets within your VPC.

Typically I use this Terraform VPC module, which has some guidance - essentially dividing a VPC into subnets like "public", "private", "intra", "database", and more. It manages creating internet gateways, NAT gateways, and route tables - pretty handy!

What you need to decide on is how large a subnet will be. What I've usually done is create a set of Public, a set of Private, and set of Database subnets.

I'll typically make each subnet size /20, which is 4,096 addresses. This means we can fit 16 subnets into a VPC of size /16 (4096*16 = 65,536).

You typically want to spread your subnets across the availability zones within a region so that your resources (servers) can be created in all available AZ's, thus reducing the chance that issues in one AZ can bring down all of your infrastructure.

Some regions only have 3 AZ's, which doesn't divide evenly into 16. That's generally fine - you can decide to "over-index" into one AZ or just waste a few IP addresses. For example, in us-east-2, I'll setup a VPC like this:

a single vpc

So, we've seen how we might divide the available IP address space into many VPC's (without overlapping IPs). This gives us the most flexibility and protection against future issues, and gives us guidance on how we "spend" IP addresses.

Even if the plan is just a spreadsheet (rather than something enforced in code), it's worth thinking about how you want to structure your private networks when going in production on AWS.

Don't miss out

Sign up to learn when new content is released! Courses are in production now.