Best Practices for setting up a VPC

Photo by Thomas Jensen on Unsplash

A Virtual Private Cloud (VPC) is one of the basic building blocks you need when running workloads on infrastructure in AWS. There are some best practices that should be followed when setting up a VPC which are outlined in this blog post.

The Basics

First of all, the VPC should span across multiple Availability Zones (AZs) available in a region. This is done by setting up a subnet in each of the AZs. Personally, I think that in most cases it should be sufficient to span only 3 AZs instead of all the ones that are actually available.

Next, in each AZ there should be one public and one private subnet. The public subnets are, as it name suggests, directly connected to the internet through an Internet Gateway. This means that the resources set up in a public subnet can be accessed from the internet and can also access the internet themselves.

To actually be able to access the servers from the internet or for the servers to access the internet it is necessary to set up the correct routing. This is done through Routing Tables associated with the subnets.

Resources that are launched into private subnets can not be reached from and communicate with the internet. Of course, there might be reasons for servers launched in private subnets to access the internet, too. For example the applications running on these servers need to access services in the public internet or they need to install software packages hosted on servers on the internet. To achieve this, so-called NAT Gateways must be launched in the public subnets and the necessary routing tables must be configured for the private subnets.


In general it’s a good advice to launch servers hosting applications or storing important data only into private subnets. Since communication from the internet is not directly possible this provides a first basic level of security. To actually make it possible to access the applications from the internet the communication should be handled by load balancers. This way, the attack surface is reduced since the load balancers provided by AWS are hardened against certain types of attacks and can also be integrated with other services, e.g. a Web Application Firewall (WAF), to further improve security.

Additionally, Security Groups need to be set up with a VPC to control which communication is allowed with the resources (servers, load balancers, etc.) launched into the VPC. A Security Group basically is a firewall that can be set up for groups of servers. It contains rules which define which sources can communicate with which targets and which ports. For example, a Security Group is probably associated with a VPC which allows SSH access to servers in the public subnets.

For each resource multiple Security Groups can be configured. This way it is possible to configure groups which only deal with certain protocols, like SSH or HTTP. Another nice feature is that a certain Security Group can be configured as the source for another Security Group.

Another level of security is provided by Network Access Control Lists (NACLs). They are used to secure the perimeter of the subnets to which they are associated. Similar to Security Groups NACLs contain the rules about what communication is allowed. To achieve this the source and target IP ranges as well as the ports are either blocked or opened.

CloudFormation Template

Setting up a VPC is a common task when new infrastructure is set up in a new AWS account or region. Therefore, it makes sense to automate this task as much as possible. To achieve this I’ve created a CloudFormation template which sets up a VPC with the following set of features:

  • Spanned across three AZs
  • One public and private subnet for each AZ
  • NAT Gateway with appropriate routing
  • Security Group allowing SSH access to bastion hosts in public subnet
  • Security Group for allowing SSH access to servers within VPC from bastion hosts
  • NACL for allowing communication only on ports 22 (SSH), HTTP (80) and HTTPS (443) from the public internet

The template for the VPC can be found at GitHub. To adjust the configuration of the VPC a set of parameters is available:

  • CIDR block for VPC
  • CIDR blocks for each subnet
  • Whether a NAT Gateway should be launched
  • The CIDR block from which SSH access is allowed to servers launched in the public subnet

For all of those parameters reasonable default values are already set. Therefore, a standard VPC can be set up by creating a CloudFormation stack using the following AWS CLI command:

aws cloudformation create-stack --stack-name my-vpc \

Alternatively, it can be launched with a click of a button when already logged in to an AWS account: Launch Stack.

The CloudFormation stack provides some outputs which are also exported. That makes it easy for other CloudFormation stacks to import these values to reference the right resources. For example, this is useful to launch other solutions, like bastion hosts or web servers, into the VPC.