Create a VPC with Public and Private Subnets
Create a VPC with public and private subnets in AWS. Follow step-by-step to set up networking for your Python backend, including troubleshooting and next steps.
Focus: create a vpc with public and private subnets
You've deployed services before, but have you ever paused to think about the network they live in? When you launch an EC2 instance or a Lambda function in AWS, it doesn't just float in the cloud — it lives inside a virtual network called a VPC. If you don't design that network intentionally, you end up with resources that are either completely exposed to the internet or completely isolated with no way to reach them. Creating a VPC with public and private subnets is the foundational skill that gives you precise control over which parts of your infrastructure are accessible and which stay protected. This lesson walks you through building exactly that.
The problem this lesson solves
Imagine you're deploying a Python web application with a PostgreSQL database. You want the web server to receive traffic from the internet, but you absolutely do not want the database to be reachable from the internet. If you launch everything in the default VPC, every resource gets a public IP by default, and your database is one security group mistake away from being exposed to the world.
The default VPC is convenient for experiments but dangerous for production. It has a single flat network, all subnets are public, and there's no concept of a private subnet where resources have no internet-routable address. Without a private subnet, you can't isolate your database or your internal application tier. You also lose fine-grained control over routing, NAT, and network access control lists (NACLs).
Creating a VPC with both public and private subnets solves this by giving you:
- Public subnets for resources that need to receive internet traffic (web servers, load balancers).
- Private subnets for resources that should not be directly reachable from the internet (databases, application servers).
- A route table per subnet so you control exactly where traffic goes.
- An Internet Gateway (IGW) to allow inbound and outbound internet traffic for public subnets.
- NAT Gateway or NAT Instance to allow private subnets to initiate outbound internet traffic (e.g., to download packages or call AWS APIs) without being reachable from the internet.
Without this structure, you're building on sand. With it, you have a network design that scales from a single script to a production microservices architecture.
Core concept / mental model
Think of a VPC as your own private data center inside AWS — a logically isolated section of the cloud. Within it, you carve out subnets — smaller network segments that group resources together.
A public subnet is like the lobby of an office building: it has a door to the street (the Internet Gateway), so anyone can walk in if you let them. A private subnet is like a server room: no direct door to the street. To get out to the internet, resources in a private subnet must go through a NAT device — think of it as a security guard who lets you leave the building but never lets strangers in.
Here's a simple word picture of the traffic flow:
Internet
|
v
Internet Gateway (IGW)
|
v
Public Route Table -> Public Subnet (e.g., 10.0.1.0/24)
| |
| v
| EC2 Web Server
|
| Private Subnet (e.g., 10.0.2.0/24)
| |
+-- NAT Gateway <------ EC2 Database (can reach out, not in)
Key terms you'll hear over and over:
- CIDR block — the IP address range your VPC or subnet covers, written like
10.0.0.0/16. - Route table — a set of rules that determine where network traffic is directed.
- Internet Gateway (IGW) — the bridge between your VPC and the internet.
- NAT Gateway — a managed service that allows outbound internet access from private subnets.
- Network ACL (NACL) — a stateless firewall at the subnet level.
- Security Group — a stateful firewall at the instance level.
How it works step by step
Creating a VPC with public and private subnets is a sequence of distinct steps. You're building a network from the ground up, so each piece depends on the one before it.
- Create the VPC — You define the IP space for your entire virtual network. A common choice is
10.0.0.0/16, which gives you 65,536 IP addresses — plenty for most designs. - Create subnets — You slice your VPC's IP range into smaller segments. You'll create at least two subnets: one public and one private. You can create them in different availability zones for high availability.
- Create an Internet Gateway — This is the door to the internet. Without it, nothing in your VPC can reach the outside world or be reached from it.
- Attach the IGW to your VPC — Just creating it isn't enough; you must explicitly attach it to your VPC.
- Create a public route table — This table tells traffic from public subnets to use the IGW for destinations outside the VPC.
- Associate the public route table with the public subnet — Now the public subnet knows how to reach the internet.
- Enable auto-assign public IP on the public subnet (optional but handy) — This ensures any EC2 instance launched there automatically gets a public IP.
- Create a NAT Gateway (or NAT instance) — This allows resources in private subnets to initiate outbound traffic. A NAT Gateway needs an Elastic IP and must live in a public subnet.
- Create a private route table — This table sends internet-bound traffic from private subnets to the NAT Gateway.
- Associate the private route table with the private subnet — Now private resources can talk to the internet (for updates, API calls) but can't be reached from the internet.
- Test and verify — Launch an instance in each subnet, check connectivity, and confirm the security boundaries.
Each step is a small, deliberate action. Rushing leads to misconfigurations like a private subnet with no route to a NAT, which silently breaks outbound connections.
Hands-on walkthrough
Let's build our VPC using both the AWS Management Console and the AWS CLI. The CLI is more repeatable and scriptable — perfect for infrastructure as code. If you haven't already, install the AWS CLI and configure your credentials with aws configure.
First, create the VPC itself. We'll use the CIDR 10.0.0.0/16.
aws ec2 create-vpc --cidr-block 10.0.0.0/16
Note the VpcId in the output. Let's say it's vpc-0abc123def456. Now create subnets in two different availability zones for resilience:
# Public subnet in us-east-1a
aws ec2 create-subnet --vpc-id vpc-0abc123def456 --cidr-block 10.0.1.0/24 --availability-zone us-east-1a
# Private subnet in us-east-1b
aws ec2 create-subnet --vpc-id vpc-0abc123def456 --cidr-block 10.0.2.0/24 --availability-zone us-east-1b
Capture the SubnetId values. Now create and attach an Internet Gateway:
aws ec2 create-internet-gateway
# Output includes InternetGatewayId, e.g., igw-123456789
aws ec2 attach-internet-gateway --internet-gateway-id igw-123456789 --vpc-id vpc-0abc123def456
Create a custom route table for the public subnet and add a route to the IGW:
aws ec2 create-route-table --vpc-id vpc-0abc123def456
# Output includes RouteTableId, e.g., rtb-public
aws ec2 create-route --route-table-id rtb-public --destination-cidr-block 0.0.0.0/0 --gateway-id igw-123456789
Associate this route table with the public subnet:
aws ec2 associate-route-table --route-table-id rtb-public --subnet-id subnet-public
Next, create a NAT Gateway in the public subnet. First, allocate an Elastic IP:
aws ec2 allocate-address --domain vpc
# Note the AllocationId
aws ec2 create-nat-gateway --subnet-id subnet-public --allocation-id eipalloc-1234
# Output includes NatGatewayId, e.g., nat-1234
Create a second route table for private subnets and route internet-bound traffic to the NAT:
aws ec2 create-route-table --vpc-id vpc-0abc123def456
# Output includes RouteTableId, e.g., rtb-private
aws ec2 create-route --route-table-id rtb-private --destination-cidr-block 0.0.0.0/0 --nat-gateway-id nat-1234
aws ec2 associate-route-table --route-table-id rtb-private --subnet-id subnet-private
That's the core network. To verify, launch a test instance in each subnet. For the public subnet, enable auto-assign public IP:
aws ec2 modify-subnet-attribute --subnet-id subnet-public --map-public-ip-on-launch
Then launch a simple Amazon Linux instance in the public subnet and one in the private subnet (use a key pair you have). Then try to SSH into the public instance and from there, ping out to the internet. For the private instance, you won't be able to reach it directly — but you should be able to start a session via the public instance (as a jump box) and confirm it can reach the internet through the NAT. That's the whole point.
Pro tip: Use the AWS Console for your first time to see all the pieces visually. The VPC dashboard shows your subnets, route tables, and gateways with clear icons. The CLI is better for repeatability.
Compare options / when to choose what
When building a VPC with public and private subnets, you make a few key choices. Here are the main ones:
| Option | Pros | Cons | Best for |
|---|---|---|---|
| Default VPC | Zero setup; all subnets public | No private layer; dangerous for prod | Quick tests, learning |
| Custom VPC (this lesson) | Full control; public/private separation | Requires design and maintenance | Production workloads |
| Single public subnet only | Simple; enough for a small web app | No isolation for databases | Tiny apps, prototypes |
| Multiple AZs | High availability; fault tolerance | More complexity and cost | Mission-critical apps |
| NAT Gateway vs NAT Instance | Managed, auto-scaled, high availability vs cheap, customizable | Cost vs manual management | Production vs small/dev environments |
For cost-sensitive environments, a NAT instance (an EC2 instance configured with iptables) is cheaper but requires you to manage the instance, its health, and failover. The NAT Gateway is fully managed and is the recommended approach for production unless cost is a major constraint.
Another choice is whether to use VPC endpoints instead of a NAT for reaching AWS services like S3 or DynamoDB. VPC endpoints keep traffic within the AWS network and are more secure and often cheaper for high-volume API calls. You can mix both.
Troubleshooting & edge cases
Even experienced engineers hit these walls. Let's solve them quickly.
1. Instances in a private subnet can't reach the internet.
- Check that the NAT Gateway is in the public subnet and has an Elastic IP.
- Verify the private route table has a 0.0.0.0/0 route pointing to the NAT Gateway.
- Ensure the NAT Gateway is available (it takes a few minutes to provision).
2. Instances in the public subnet don't have a public IP.
- Enable auto-assign public IP on the subnet, or assign an Elastic IP manually.
3. You can't SSH into your public instance. - Check the security group inbound rule for port 22 from your IP. - Verify the route table is associated correctly and the IGW is attached.
4. Private subnet can't reach services like pip install or apt-get.
- Confirm the NAT is up and the route exists. Also, if you're using a VPC endpoint for S3, make sure the route for a prefix list exists.
5. You accidentally deleted a route table. - No faster fix: AWS auto-creates a default route table for the VPC. You can recreate your custom ones referencing it.
What you learned & what's next
You now understand the core idea behind creating a VPC with public and private subnets: you're implementing network isolation with controlled connectivity. You completed a hands-on exercise that builds the full network — VPC, subnets, IGW, route tables, and NAT — and you know how to compare different network designs based on cost, availability, and simplicity.
This is the foundation for many advanced AWS topics. Next in this track, you'll likely explore Security Groups and NACLs — how to actually filter traffic at the instance and subnet level — or VPC Peering to connect VPCs. You'll also use this VPC knowledge when you deploy EC2 instances for your Python apps, set up RDS databases in private subnets, or design serverless architectures with Lambda inside a VPC.
Remember: your VPC is the invisible skeleton that holds your AWS world together. Get this right, and the rest of your infrastructure has a solid place to stand.
Practice recap
Now that you've built your VPC, test it by launching two EC2 instances — one in the public subnet and one in the private. SSH into the public instance, then from there try to SSH into the private instance. Confirm that the private instance can ping 8.8.8.8 (outbound works via NAT) but your local machine cannot reach it directly. That proves your network design is correct.
Common mistakes
- Forgetting to attach the internet gateway to the VPC — without this, even public subnets can't reach or be reached from the internet.
- Putting a NAT gateway in a private subnet instead of a public subnet — it needs a route to the internet via an IGW, otherwise it won't work.
- Not updating the private route table after creating the NAT gateway — the default route still points nowhere, so private subnets have no outbound access.
- Using the default VPC for production and wondering why your database is publicly accessible — the default VPC has no private subnets to isolate sensitive resources.
Variations
- Use a NAT instance (an EC2 instance with iptables) instead of a managed NAT Gateway to save cost in dev/test environments.
- Implement VPC endpoints for AWS services like S3 and DynamoDB — these keep traffic inside AWS and avoid NAT data transfer costs.
- Use Terraform or AWS CloudFormation to define your VPC as code for reproducible, reviewable infrastructure.
Real-world use cases
- Deploy a web application with a public-facing load balancer in a public subnet and an RDS database in a private subnet, accessible only to the app.
- Run a batch processing job on EC2 instances in a private subnet that need to download packages from the internet via NAT but never accept inbound web traffic.
- Create separate VPCs for development and production with identical public/private structure, then use VPC peering to allow controlled communication.
Key takeaways
- A VPC is your private network in AWS; public subnets have direct internet access via an Internet Gateway, private subnets do not.
- Private subnets communicate with the internet through a NAT Gateway, which allows outbound only, preserving inbound security.
- The route tables are the heart of VPC networking — each subnet must have an associated route table with the correct default route.
- You can build the entire VPC structure using the AWS CLI, which is scripting-friendly and repeatable.
- Isolating your database or backend in a private subnet is essential for production security and is the core reason to create this architecture.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.