Beginner’s guide to AWS VPC

I have observed that developers new to AWS, often struggle to understand cloud networking let alone provision a secure Virtual Private Cloud (VPC). Well, with so many frameworks and APIs at your disposal, programming and software development have abstracted to a level that most application developers might not have spent time in understanding networking infrastructure and its impact on applications.

In this article, I will explain VPC, Subnet, NAT, IGW, Route Tables, etc. to help app developers, not from a networking background, learn basic functioning of these services. I have also included aws commands to provision a secure VPC.

The deployment architecture to provision is as shown below.

Sample deployment diagram of a secure VPC

Virtual Private Cloud (VPC)

Think of VPC as a virtual network created by AWS for your account. It is completely isolated from VPCs of other users, which allows only you or any AWS account authorized by you to use or modify it. This is your network in the cloud.

So, why you need VPC? It is required to provision EC2 instances, DB instances, any service running atop EC2, or any compute instances where applications need some sort of network communications. Given the importance of VPC, AWS creates a default VPC for every user in every region. You also get a default Internet Gateway (IGW) attached to the default VPC. As the name suggests, IGW allows you to access the internet within a VPC. It is bidirectional access (source of connection can be either a system on the internet or any EC2 with public IP in VPC).

VPC is specific to the Region in which it was created. You provide an IP Address range which is in a CIDR notation to create a VPC. I highly recommend getting familiar with CIDR notations and IP Address ranges. Check AWS recommended IPv4 Address Planner to plan subnet CIDR. AWS uses “” as the default in every region. This CIDR range provides 65,534 IP Addresses (2^16) for hosts.

AWS reserves the first four and the last IPv4 address in each subnet’s CIDR block. They’re not available for use.

  • Network Address: x.y.z.0
  • Router: x.y.z.1
  • DNS: x.y.z.2
  • Reserved for future: x.y.z.3
  • Broadcast: x.y.255.255


After you have created a VPC, the next logical step is to partition it into subnets. Similar to a traditional network, a subnet represents a logical part of your network and acts as a group with self-contained security policies. Your resource (EC2) virtually resides inside a subnet and communicate to resources in other subnets using route tables.

Each subnet is associated with a CIDR taken from the parent VPC. A subnet CIDR lets you determine how many private IP Addresses are available for this subnet.

# Create a VPC using as CIDR
aws ec2 create-vpc --cidr-block --tag-specifications ResourceType=vpc,Tags=[{Key=Name,Value=testvpc}]

# Above command will produce a JSON output out of which note the VpcId
    "Vpc": {
        "CidrBlock": "",
          . . .
        "VpcId": "vpc-04bfd0a2f9bb6114a",

# Next create first subnet for public instances (direct route to Internet Gateway)
aws ec2 create-subnet --vpc-id vpc-04bfd0a2f9bb6114a --cidr-block --availability-zone ap-south-1a --tag-specifications ResourceType=subnet,Tags=[{Key=Name,Value=public-subnet-testvpc}]

# Second subnet for private instances (access to internet via NAT Gateway)
aws ec2 create-subnet --vpc-id vpc-04bfd0a2f9bb6114a --cidr-block --availability-zone ap-south-1b --tag-specifications ResourceType=subnet,Tags=[{Key=Name,Value=private-subnet-testvpc}]

These two subnets look as following in the AWS console. Notice that each Subnet, though the allocated quota is of 128 IP Addresses it shows 123 because of AWS reserving 5 IP Addresses. Also, AWS has created a Route Table and Network ACL for our VPC and associated them with new Subnets.

Screenshot from AWS console

Internet Gateway (IGW)

The Internet Gateway allows internet access between instances in VPC and the internet. IGW provides bi-directional or duplex communication and is a fully managed service (horizontally scaled, redundant, and highly available). AWS creates a default IGW and attaches it to your default VPC. This allows users to start accessing their instances created in default VPC from the internet is without much configuration. Internet Gateway is associated with a VPC and not subnets.

# create a new Internet Gateway (igw) and note down the ID it returned
aws ec2 create-internet-gateway 

# attach this IGW to new VPC
aws ec2 attach-internet-gateway --vpc-id vpc-04bfd0a2f9bb6114a --internet-gateway-id igw-00dade6e6f6b3049c

# finally, to allow traffic update the route table for this VPC
aws ec2 create-route --route-table-id rtb-0fb72edab8223338d --destination-cidr-block --gateway-id igw-00dade6e6f6b3049c

So now our route table has a route to Internet via IGW. This route table is by default associated with both subnets hence allowing them to reach the internet and users from internet to reach these two subnets. Let us create a new route table and associate it with private subnet.

Every new route table is auto-populated to allow traffic within the CIDR (local route)

# create a new route table and note down the ID
aws ec2 create-route-table --vpc-id vpc-04bfd0a2f9bb6114a

# associate this route table to private-subnet created above
aws ec2 associate-route-table  --subnet-id subnet-06bb27a382953fc62 --route-table-id rtb-046b424b6beba4460

# associate the existing route table to public-subnet
aws ec2 associate-route-table  --subnet-id subnet-0295f8f0c70f7123f --route-table-id rtb-0fb72edab8223338d

Next, we will create a NAT gateway and update the private subnet so that its traffic doesn’t go directly to IGW.

Network Address Translation Gateway (NAT Gateway)

It enables instances in a private subnet to connect to the internet or other AWS services, but prevent the internet from initiating a connection with those instances. It helps in abstracting your backend network (private IP Addresses) from public network and denies any traffic not originating from private subnet. The most common use case is when you need to patch servers or download and install software packages from a valid repository. A precondition for NAT is to have an Elastic IP Address (static, public IPv4 address) from AWS.

# first get an Elastic IP
aws ec2 allocate-address --domain vpc

# next, create a NAT Gateway (using the allocation-id from above) and associate with public-subnet
aws ec2 create-nat-gateway --allocation-id eipalloc-0c2ca1171feb46f6c --subnet-id subnet-0295f8f0c70f7123f

# finally, add a route in private-subnet's route table to use NAT Gateway for all IPs
aws ec2 create-route  --route-table-id rtb-046b424b6beba4460 --destination-cidr-block --nat-gateway-id nat-03cc5aa873b4e5d41

In networking, represents all IP Addresses. This entry will allow VPC to route any traffic having destination IP Address not matching within to the NAT Gateway.

AWS Console – Entry in route table for NAT Gateway

Route Tables

In simple terms, a route table is a lookup table, which defines where to route network traffic. This traffic can be originated from a subnet (within AWS) or the internet. Wait, doesn’t this sound like a Router? You’d be correct in that observation. Please note AWS has a magic router for a VPC but it’s hidden from users and of course, won’t show or give access to their router (which makes perfect sense for a cloud provider). 

As a user, we can define the routing, using a “route table” for our VPCs and Subnets. An important point here – AWS does not share the physical or virtual interfaces where packets were received (ingress) and from where packets are sent (egress). When a VPC is created, AWS creates a default route table which has an entry ensuring all local traffic is matched and forwarded appropriately. Any subnet created within a VPC is by default associated with a “main route table.”

VPC Endpoints (Private access to AWS services)

To further secure network traffic, AWS allows us to create a VPC endpoints, using which instances skip going to Internet and access services like S3 entirely within AWS realm. All the traffic is within AWS network (secured and encrypted) without the need to expose buckets to public. If you are using containers then recommendation is to have an endpoint between your VPC and ECR.

# create a vpc endpoint of type gateway and associate with public subnet's route table
aws ec2 create-vpc-endpoint --vpc-endpoint-type Gateway --vpc-id vpc-04bfd0a2f9bb6114a --service-name com.amazonaws.ap-south-1.s3 --route-table-ids rtb-0fb72edab8223338d

The above command does following

  • Create a VPC Endpoint of type Gateway to access an AWS Service, which in this case is “S3” in region ap-south-1
  • Associate the route-table “rtb-0fb72edab8223338d” to this VPC endpoint
  • Add a route in “rtb-0fb72edab8223338d” route table to access the destination service
AWS Console – VPC endpoint to S3

Next, to have secure access to ECR, we need to create “Interface” VPC endpoint.

# used by Docker Registry APIs.
aws ec2 create-vpc-endpoint --vpc-endpoint-type Interface --vpc-id vpc-04bfd0a2f9bb6114a --service-name com.amazonaws.ap-south-1.ecr.dkr --subnet-id subnet-06bb27a382953fc62

# used by Amazon ECR API
aws ec2 create-vpc-endpoint --vpc-endpoint-type Interface --vpc-id vpc-04bfd0a2f9bb6114a --service-name com.amazonaws.ap-south-1.ecr.api --subnet-id subnet-06bb27a382953fc62

# to send log information to CloudWatch Logs (used by awslogs log driver)
aws ec2 create-vpc-endpoint --vpc-endpoint-type Interface --vpc-id vpc-04bfd0a2f9bb6114a --service-name com.amazonaws.ap-south-1.logs --subnet-id subnet-06bb27a382953fc62
AWS console – VPC endpoints

Network Access Control Lists (NACL)

The name is self-explanatory as a set of rules which govern the allowed and denies traffic into your subnets. This is a security feature that works at the subnet level and can be thought of as a hidden firewall sitting behind the hidden router. Default NACL allows all inbound and outbound IPv4 traffic but any NACL created by users denies all inbound and outbound traffic until you add rules to allow it.

There are three parts of NACL that are important to developers:

  • Inbound Rules
  • Outbound Rules
  • Subnet associations

Rules for both inbound and outbound function independently and must be configured as a pair for bi-direction traffic. In simple terms, if you add an inbound rule to allow HTTP traffic but forgot to add an outbound rule for the same, then AWS will allow incoming HTTP requests but deny HTTP response, causing your application to experience HTTP time-out exception.

are evaluated starting with the lowest numbered rule. As soon as a rule matches traffic, it’s applied regardless of any higher-numbered rule that may contradict it. This is a first-match policy from the lowest rule number to the highest. A rule number marked as “*” matches all traffic, is of the lowest priority and should DENY.

One of the most common mistakes I have seen in NACLs is to assume the ports for both ingress and egress traffic will be the same. Often the client application uses an ephemeral port. Servers use standard or documented port numbers. So, if you are hosting a web application and allow port 80 for inbound traffic then for outgoing, you should allow all ports or the traffic will not reach the client. Remember inbound needs a source port and outbound needs a destination port.

Security Group (SG)

A security group is a more granular level of security feature provided by AWS. It is associated with network interfaces and not with a subnet. When you create an EC2 instance, AWS creates a network interface (eth0) for the EC2 and the security group is associated with this eth0 and not with subnet or EC2. Tomorrow, if you add this interface to a different EC2 instance then the associated SG rules will apply to the new EC2.

As an analogy, this is a virtual firewall for your compute instances that reside behind the NACL. Users don’t need to configure and maintain both NACLs and SGs; in fact, you will be working with SG more often than NACL because of the default NACL all-allow policy. As with both inbound and outbound rules, AWS creates a default SG when you create VPC.

Security Groups are different from NACL in the following ways:

  • All rules are evaluated to decide whether to allow or reject
  • There are no “deny” rules, you simply state protocol:port to be allowed
  • For all allowed incoming traffic, SG automatically allows outgoing (stateful firewall) traffic
  • If there is more than one rule for the same port then AWS considers the most permissive rule. According to the documentation: “… if you have a rule that allows access to TCP port 22 (SSH) from IP address and another rule that allows access to TCP port 22 from everyone, everyone has access to TCP port 22.”
  • As a best practice, except for public subnets, be specific in the rules and follow the principle of least privilege.


# delete vpc endpoints
aws ec2 delete-vpc-endpoints --vpc-endpoint-ids vpce-0b1f710745f5d3b27 vpce-09fd7cffd58b6f303 vpce-09f5c94804d5cb3de vpce-015935ad6ee400f77

# delete NAT Gateway
aws ec2 delete-nat-gateway --nat-gateway-id nat-03cc5aa873b4e5d41

# release Elastic IP used to create NAT Gateway
aws ec2 release-address --allocation-id eipalloc-0c2ca1171feb46f6c

# delete VPC created for this tutorial
aws ec2 delete-vpc --vpc-id vpc-04bfd0a2f9bb6114a

In case, you are facing difficulty from the command-line then login to AWS console and delete the components.

This article is an updated version of my blog published here.


Categories: aws, security, vpc

Tagged as: , ,

2 replies »

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s