Deploy a Secure VPC Network with Terraform

Build a custom AWS VPC with public and private subnets using Terraform.

Introduction

30 Second Summary

Every app you use online lives inside a network. Companies spend huge amounts of money hiring engineers who can build those networks securely, keeping sensitive data protected while still letting the right traffic through.

In this project, you will build a complete cloud network on AWS using Terraform. You will create a custom VPC with public and private subnets, configure security groups, and deploy a live web server that anyone can access from their browser.

What You'll Build

A secure cloud network architecture on AWS, built entirely through Infrastructure as Code with Terraform.

By the end of this project, you'll have:

  • A Terraform project that defines your entire cloud infrastructure as code, ready to show recruiters.
  • A custom VPC with a public subnet connected to the internet and a private subnet isolated from outside traffic.
  • A running EC2 web server you can access from your browser, deployed and secured through Terraform.
  • Secret Mission: Add a private EC2 instance with a NAT Gateway so it can reach the internet for updates while remaining invisible to the outside world.

Are there any prerequisites?

You should have a basic understanding of networking concepts like IP addresses and ports. Familiarity with using the command line is helpful too.

No prior AWS or Terraform experience is needed. This project walks you through everything from account creation to deployment.

Before We Start

Before We Start...

Set Up Your AWS Account

Before you can deploy any cloud infrastructure, you need an AWS account. This is your gateway to all of Amazon's cloud services.

In this step, you will create a free AWS account, set up a dedicated user for programmatic access, and verify everything works. This gives Terraform the credentials it needs to create resources on your behalf.

In this step, get ready to:
  • Create an AWS Free Tier account with $100 in sign-up credits.
  • Create an IAM user with programmatic access for Terraform.
  • Verify your account is active by viewing the EC2 dashboard.
Create an AWS Free Tier account

AWS offers a Free Tier that gives new customers $100 in credits at sign-up. You will not be charged for anything in this project.

  • Open the AWS Free Tier page at https://aws.amazon.com/free/ in your browser.
  • Click Create a Free Account.
  • Enter your email address, choose a password, and pick an account name.
  • Choose Personal as the account type.
  • Enter your personal information (name, address, phone number).
  • Enter a credit or debit card when prompted.

Will I be charged?

No. AWS requires a card on file to prevent fraud, but you will not be charged for this project. New accounts receive $100 in credits at sign-up, and the free account plan ensures no charges for up to 6 months.

  • Complete the phone verification step. AWS will call or text you with a PIN.
  • Select the Free plan when asked to choose between Free and Paid.
  • Wait for the activation email from AWS. This usually takes a few minutes but can take up to 24 hours.

Account activation taking too long?

Check your email and spam folder for a confirmation message from AWS. You can sign in to your account while activation is in progress, but some services will not be available until activation completes.

Help me with my account activation.

Create an IAM user for programmatic access

Your root account has full power over everything in AWS. Using it directly is risky. Instead, you will create a separate IAM user specifically for Terraform to use when creating resources.

  • Sign in to the AWS Management Console at https://console.aws.amazon.com/.
  • In the search bar at the top, type IAM and select IAM from the results.
  • In the left navigation pane, click Users.
  • Click Create user.
  • On the Specify user details page, enter terraform-user as the user name.
  • Click Next.
  • On the Set permissions page, select Attach policies directly.
  • In the search box, type AdministratorAccess and select the checkbox next to it.
  • Click Next, then click Create user.

What is IAM?

IAM (Identity and Access Management) lets you create users and control what they can do in your AWS account. The AdministratorAccess policy gives this user full permissions to create and manage resources.

In a production environment, you would use more restrictive policies. For learning purposes, administrator access keeps things simple.

Now you need to generate an Access Key so Terraform can authenticate as this user.

  • Click on the user name terraform-user to open the user details page.
  • Select the Security credentials tab.
  • In the Access keys section, click Create access key.
  • On the use case page, select Command Line Interface (CLI).
  • Check the confirmation checkbox at the bottom, then click Next.
  • Skip the optional description tag and click Create access key.
  • Click Download .csv file to save your Access Key ID and Secret Access Key to a safe location on your computer.
  • Click Done.

Save your keys now

This is your only opportunity to view or download the Secret Access Key. After you close this page, you cannot retrieve it again. If you lose it, you must delete the key and create a new one.

Store the downloaded .csv file somewhere secure on your computer. You will need both keys in the next step when configuring the AWS CLI.

Don't see the Create access key button?

Make sure you are on the Security credentials tab of your user (not the Permissions tab). If the button is greyed out, you may already have two access keys for this user. Delete one first.

Help me create an access key for my IAM user.

Verify your account is active

A quick check confirms everything is working before you move on to installing tools.

  • In the AWS Console search bar at the top, type EC2 and select EC2 from the results.
  • Confirm you can see the EC2 Dashboard page. You should see sections like Resources and Launch instance.

If you can see the EC2 dashboard, your account is active and ready to use.

Your AWS account is ready and your IAM user has the credentials Terraform needs. Next up, you will install Terraform and the AWS CLI on your machine so you can start writing infrastructure code.

Install Terraform and AWS CLI

Your AWS account is ready and your IAM credentials are saved. Now it's time to install the two command-line tools that power your cloud engineering workflow.

Terraform is an Infrastructure as Code tool that translates configuration files into real cloud resources. The AWS CLI connects your terminal directly to your AWS account. Together, they let you build and manage infrastructure without clicking through the console.

In this step, get ready to:
  • Install the AWS CLI on your machine.
  • Install Terraform on your machine.
  • Connect the AWS CLI to your AWS account using your access keys.
Install the AWS CLI

The AWS CLI lets you run AWS commands directly from your terminal instead of clicking through the web console.

  • Check if the AWS CLI is already installed by opening a new Command Prompt window and running:
aws --version

✔️ I see a version number

If you see output like aws-cli/2.27.41 Python/3.11.6 Windows/10 exe/AMD64, your AWS CLI is ready. Skip ahead to the Terraform installation below.

ⓧ Command not found

The AWS CLI is not installed yet. Follow these steps to install it.

  • Download the AWS CLI installer by running this command in Command Prompt:
msiexec.exe /i https://awscli.amazonaws.com/AWSCLIV2.msi

What does this command do?

This uses Windows' built-in msiexec tool to download and run the official AWS CLI installer directly from Amazon's servers. A setup wizard will appear on your screen.

  • Follow the setup wizard. Accept the default settings and click through each screen until the installation finishes.
  • Close your Command Prompt window and open a new one. This is required for the new command to be recognized.
  • Verify the installation by running:
aws --version

You should see output like aws-cli/2.27.41 Python/3.11.6 Windows/10 exe/AMD64. The exact version numbers may differ.

Still seeing 'command not found'?

Make sure you closed and reopened your Command Prompt after the installation. The old window does not know about newly installed programs.

If it still does not work, try restarting your computer and opening a fresh Command Prompt.

Help me troubleshoot why aws --version is not working after installing the AWS CLI.

Install Terraform

Terraform translates .tf configuration files into real cloud resources. This project requires Terraform version 1.2 or higher.

  • Check if Terraform is already installed by running this in Command Prompt:
terraform -version

✔️ I see version 1.2 or higher

If you see output starting with Terraform v1.2 or any higher number (like v1.15.5), Terraform is ready. Skip ahead to configuring the AWS CLI below.

ⓧ I see an older version

If your version number starts below 1.2, you need to upgrade. The easiest way on Windows is to reinstall using Chocolatey.

  • Open PowerShell as Administrator by pressing the Windows key, typing PowerShell, right-clicking Windows PowerShell, and selecting Run as administrator.
  • Upgrade Terraform by running:
choco upgrade terraform
  • Close and reopen Command Prompt, then verify with:
terraform -version

You should now see Terraform v1.15.5 or higher.

Don't have Chocolatey?

If the choco command is not recognized, follow the manual install instructions in the "Command not found" tab instead. Those steps work without Chocolatey.

ⓧ Command not found

Terraform is not installed yet. You can install it using Chocolatey (a Windows package manager) or by downloading the binary directly.

Install with Chocolatey

  • Open PowerShell as Administrator by pressing the Windows key, typing PowerShell, right-clicking Windows PowerShell, and selecting Run as administrator.
  • Install Terraform by running:
choco install terraform
  • Close and reopen Command Prompt after the installation finishes.
  • Verify the installation by running:
terraform -version

You should see Terraform v1.15.5 or a similar recent version number.

Install manually (no Chocolatey)

  • Go to the Terraform install page in your browser.
  • Under the Windows section, download the AMD64 zip file.
  • Extract the zip file to a folder on your computer (for example, C:\terraform).
  • Add that folder to your system PATH. Press the Windows key, type environment variables, and select Edit the system environment variables.
  • Click Environment Variables, find Path under System variables, click Edit, and add a new entry with the folder path (for example, C:\terraform).
  • Click OK on all dialogs to save.
  • Close and reopen Command Prompt, then verify by running:
terraform -version

You should see Terraform v1.15.5 or a similar recent version number.

Terraform command still not recognized?

Make sure you opened a brand new Command Prompt window after installing. Existing windows do not pick up PATH changes.

If you used the manual install method, double-check that the folder containing terraform.exe is the one you added to your PATH (not the zip file itself).

Help me troubleshoot why terraform -version is not working on Windows.

Configure the AWS CLI

Now that both tools are installed, you need to tell the AWS CLI which account to connect to. This is where the Access Key ID and Secret Access Key from Step 1 come in.

  • In Command Prompt, start the configuration wizard by running:
aws configure

What does this command do?

This command launches an interactive prompt that asks for four pieces of information. It stores your credentials locally so the AWS CLI knows which account to use for every future command.

  • When prompted for AWS Access Key ID, paste the Access Key ID you saved from Step 1.
  • When prompted for AWS Secret Access Key, paste the Secret Access Key you saved from Step 1.
  • When prompted for Default region name, type:
us-east-1
  • When prompted for Default output format, type:
json

Why us-east-1?

This region has the broadest service availability on AWS and is the default used in most tutorials. All resources you create in this project will live in this region.

  • Verify your configuration worked by running:
aws sts get-caller-identity

You should see a JSON response containing your account number, user ID, and ARN. This confirms your terminal is connected to your AWS account.

Seeing 'Unable to locate credentials'?

Run aws configure again and carefully re-enter your Access Key ID and Secret Access Key. Make sure there are no extra spaces before or after the values.

If you lost your Secret Access Key, go back to the IAM console in AWS, delete the old access key, and create a new one.

Help me fix the 'Unable to locate credentials' error with the AWS CLI.

Your tools are installed and connected. Next up, you will write your first Terraform configuration file and initialize your project.

Write the Terraform Provider Configuration

Your tools are installed and your AWS credentials are configured. Now it's time to write the code that tells Terraform which cloud provider to use and how to connect to it.

Terraform uses configuration files to understand your infrastructure. Without these files, Terraform has no idea which cloud provider to talk to, which version of the provider plugin to download, or which region to deploy resources in.

In this step, get ready to:
  • Create your Terraform project folder and open it in VS Code.
  • Write configuration files that connect Terraform to AWS.
  • Initialize the project to download the AWS provider plugin.
Create your project folder and terraform.tf

Every Terraform project lives in its own folder. You'll create one on your Desktop so it's easy to find later.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir aws-vpc-project
cd aws-vpc-project
  • Open this folder in VS Code by running:
code .

code . not working?

If you see "command not found", open VS Code manually instead. Press Cmd+Space (macOS) or the Windows key (Windows), type VS Code, and press Enter.

Then click File in the top menu bar, select Open Folder, navigate to your Desktop, select the aws-vpc-project folder, and click Open.

Help me open my project folder in VS Code

VS Code opens with the aws-vpc-project folder in the Explorer sidebar on the left.

Now create your first Terraform file. This file tells Terraform which provider plugin to download and which versions are compatible.

  • In VS Code's Explorer sidebar, click the New File icon (a page with a plus sign).
  • Name the file terraform.tf and press Enter.
  • Paste the following code into terraform.tf:
# Define the required provider and version constraints
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.92"
    }
  }

  # Minimum Terraform version needed for this project
  required_version = ">= 1.2"
}

What does this code do?

  • required_providers tells Terraform to download the AWS provider plugin from HashiCorp's registry.
  • version = "~> 5.92" means Terraform can use any version from 5.92 up to (but not including) 6.0. This keeps your project stable while still getting patches.
  • required_version = ">= 1.2" ensures anyone running this project has at least Terraform 1.2 installed.
  • Save terraform.tf by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • Confirm the file appears in the Explorer sidebar under aws-vpc-project.

Don't see terraform.tf in the sidebar?

Make sure you created the file inside the aws-vpc-project folder, not at a higher level. Check that the Explorer sidebar shows aws-vpc-project as the top-level folder name.

Help me find where my terraform.tf file went

✔️ Awesome, I've got everything!

Great. Double check you've saved your file, then move on to the next file.

ⓧ I'd like to double check the full code

Here is the complete terraform.tf file for comparison:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.92"
    }
  }

  required_version = ">= 1.2"
}
Create the provider configuration

The provider configuration tells the AWS plugin which region to deploy your resources in. You'll use us-east-1 because it has the broadest service availability.

  • In VS Code's Explorer sidebar, click the New File icon again.
  • Name the file provider.tf and press Enter.
  • Paste the following code into provider.tf:
# Configure the AWS provider with your target region
provider "aws" {
  region = "us-east-1"
}

What does this code do?

The provider block configures how Terraform connects to AWS. Setting region = "us-east-1" means every resource you create will live in the US East (N. Virginia) data center.

Terraform picks up your AWS credentials automatically from the aws configure setup you completed in the previous step. You don't need to hardcode any keys in your Terraform files.

  • Save provider.tf by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • Confirm both terraform.tf and provider.tf appear in the Explorer sidebar.

File saved in the wrong place?

Both files must be in the same aws-vpc-project folder. If a file ended up somewhere else, right-click it in the Explorer and select Delete, then create it again inside the correct folder.

Help me organize my Terraform files in the right folder

✔️ Awesome, I've got everything!

Great. Double check you've saved your file, then move on to initializing Terraform.

ⓧ I'd like to double check the full code

Here is the complete provider.tf file for comparison:

provider "aws" {
  region = "us-east-1"
}
Initialize the Terraform project

Terraform needs to download the AWS provider plugin before it can do anything with your configuration. The terraform init command reads your terraform.tf file and fetches the correct plugin version.

  • Open VS Code's integrated terminal by pressing Ctrl+` (backtick key, below Esc).
  • Confirm the terminal shows aws-vpc-project as the current directory.
  • Initialize the Terraform project by running:
terraform init

What does this command do?

  • terraform init reads your configuration files and downloads the provider plugins they reference.
  • It creates a .terraform folder to store the downloaded plugin.
  • It also creates a .terraform.lock.hcl file that locks the exact provider version for reproducibility.
  • Look for the message Terraform has been successfully initialized! in the terminal output.

You should also see a line mentioning the AWS provider version that was installed.

Seeing an error during init?

If you see a "no configuration files" error, your terminal is not in the aws-vpc-project folder. Run cd ~/Desktop/aws-vpc-project and try again.

If you see a network or registry error, check your internet connection and run terraform init again. The HashiCorp registry occasionally has brief timeouts.

Help me troubleshoot terraform init errors

Your Terraform project is initialized and ready to define real cloud infrastructure. Next up, you'll write the code that creates a custom VPC with public and private subnets.

Define the VPC and Subnets

Your Terraform project is initialized and connected to AWS. Now it is time to define the actual cloud infrastructure you want to build.

Every cloud application lives inside a Virtual Private Cloud (VPC). A VPC is your own isolated section of the AWS cloud where you control the network layout. Inside that VPC, you divide the space into subnets to separate resources that need internet access from those that should stay private.

In this step, get ready to:
  • Define a custom VPC with its own IP address range.
  • Create a public subnet for internet-facing resources.
  • Create a private subnet for isolated resources.
Create the VPC resource

The VPC defines the overall network boundary for your project. Every other networking resource you create will live inside it.

  • In VS Code, create a new file called network.tf inside your aws-vpc-project folder.
  • Add the VPC resource by pasting this code into network.tf:
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "internship-vpc"
  }
}

What does this code do?

This block tells Terraform to create a VPC resource on AWS with the internal name main.

  • cidr_block = "10.0.0.0/16" reserves a range of 65,536 private IP addresses for your network.
  • enable_dns_hostnames = true lets AWS assign readable DNS names to instances inside the VPC.
  • The tags block gives your VPC a human-friendly label in the AWS Console.
  • Save network.tf.
  • Confirm the file was created by checking the VS Code Explorer sidebar. You should see network.tf listed alongside terraform.tf and provider.tf.

Don't see network.tf in the sidebar?

  • Make sure you saved the file with the exact name network.tf (not network.tf.txt or Network.tf).
  • Check that the file is inside the aws-vpc-project folder, not in a parent or nested folder.

Still stuck? Why isn't my network.tf file showing in VS Code?

Define the public subnet

Subnets carve your VPC into smaller network segments. The public subnet will host resources that need direct internet access, like web servers.

  • In network.tf, add the public subnet resource below the VPC block:
resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = "us-east-1a"
  map_public_ip_on_launch = true

  tags = {
    Name = "public-subnet"
  }
}

What does this code do?

This creates a subnet inside your VPC with 256 IP addresses (the /24 range).

  • vpc_id = aws_vpc.main.id links this subnet to the VPC you just defined. Terraform automatically resolves the reference.
  • map_public_ip_on_launch = true gives every instance launched here a public IP address automatically.
  • availability_zone = "us-east-1a" places the subnet in a specific data center within the region.
  • Save network.tf. You will validate the complete file after adding the private subnet next.
Define the private subnet

The private subnet isolates resources that should never be directly reachable from the internet. Databases and internal services typically live here.

  • In network.tf, add the private subnet resource below the public subnet block:
resource "aws_subnet" "private" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = "us-east-1a"

  tags = {
    Name = "private-subnet"
  }
}

What makes this subnet private?

Notice that map_public_ip_on_launch is missing from this block. Without it, instances in this subnet will not receive a public IP address.

The CIDR block 10.0.2.0/24 gives this subnet its own separate pool of 256 IP addresses that do not overlap with the public subnet.

  • Save network.tf.
  • Verify your configuration is syntactically correct by running this command in the VS Code terminal:
terraform validate
  • You should see Success! The configuration is valid. in the output.

Seeing a validation error?

  • Check that every opening brace { has a matching closing brace } in your file.
  • Make sure you used aws_vpc.main.id (not just main.id) when referencing the VPC from each subnet.
  • Confirm there are no typos in resource type names like aws_subnet or attribute names like cidr_block.

Still stuck? Help me fix a terraform validate error in my network.tf file

✔️ Awesome, I've got everything!

Great. Double check you have saved network.tf and that terraform validate passes.

ⓧ I'd like to double check the full code

Here is the complete network.tf file with all three resources:

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "internship-vpc"
  }
}

resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = "us-east-1a"
  map_public_ip_on_launch = true

  tags = {
    Name = "public-subnet"
  }
}

resource "aws_subnet" "private" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = "us-east-1a"

  tags = {
    Name = "private-subnet"
  }
}

Your network foundation is in place. Next up, you will connect the public subnet to the internet so resources inside it can actually communicate with the outside world.

Configure Internet Access

Your VPC now has a public subnet and a private subnet, but neither can actually reach the internet yet.

In cloud networking, a subnet needs an explicit path to the outside world. That path starts with an Internet Gateway (the door to the internet) and a Route Table (the directions telling traffic where to go). Without both, resources in the subnet are isolated.

In this step, get ready to:
  • Create an Internet Gateway and attach it to your VPC.
  • Define a route table that sends outbound traffic through the gateway.
  • Associate the route table with your public subnet.
Add an internet gateway to your VPC

An Internet Gateway is a managed AWS component that connects your VPC to the public internet. Without it, nothing inside the VPC can send or receive traffic from outside.

  • In VS Code, open network.tf and scroll to the bottom of the file (below your aws_subnet "private" resource).
  • Add the Internet Gateway resource by pasting this code at the bottom of the file:
resource "aws_internet_gateway" "igw" {
  # Attach this gateway to our VPC
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "internship-igw"
  }
}

What does this code do?

This creates an Internet Gateway and attaches it to your VPC using aws_vpc.main.id. The tag internship-igw gives it a human-readable name in the AWS Console.

Think of it like installing a front door on a building. The door exists, but you still need signs (routes) telling people how to find it.

  • Save network.tf.
  • You won't see a visible change yet. The next chunk adds the route table that completes the internet path.
Create a route table and link it to the public subnet

A route table contains rules that determine where network traffic goes. You need one that says "any traffic headed for the internet should go through the Internet Gateway."

  • In network.tf, add the route table and route table association below the Internet Gateway resource you just created:
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  # Send all internet-bound traffic to the Internet Gateway
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }

  tags = {
    Name = "public-rt"
  }
}

resource "aws_route_table_association" "public" {
  # Link this route table to the public subnet
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

What does this code do?

The route table resource defines a rule: any traffic going to 0.0.0.0/0 (which means "anywhere on the internet") gets routed through your Internet Gateway.

The route table association links this route table to your public subnet. Without this association, the subnet would not know to use these routing rules. Your private subnet deliberately has no association to this table, keeping it isolated from the internet.

  • Save network.tf.
  • Confirm your configuration is valid by running this command in your VS Code terminal:
terraform validate
  • You should see Success! The configuration is valid. in the output.

Seeing a validation error?

  • Check that every opening brace { has a matching closing brace }. A missing brace is the most common cause of syntax errors.
  • Make sure resource names match exactly. For example, aws_internet_gateway.igw.id in the route table must reference the gateway you named "igw" above.
  • Confirm you ran terraform init in a previous step. Validation requires an initialized directory.

Still stuck? Help me debug my terraform validate error in network.tf

✔️ Awesome, I've got everything!

Great. Double check you have saved network.tf before moving on.

ⓧ I'd like to double check the full code

Here is the complete network.tf file at this point:

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "internship-vpc"
  }
}

resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = "us-east-1a"
  map_public_ip_on_launch = true

  tags = {
    Name = "public-subnet"
  }
}

resource "aws_subnet" "private" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = "us-east-1a"

  tags = {
    Name = "private-subnet"
  }
}

resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "internship-igw"
  }
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }

  tags = {
    Name = "public-rt"
  }
}

resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

Your public subnet now has a complete path to the internet. Next up, you'll create security groups to control exactly which traffic is allowed in and out of your resources.

Create Security Groups

Your VPC has subnets and internet access configured. But right now, nothing controls which traffic is actually allowed to reach your resources.

In this step, you will create a Security Group that acts as a virtual firewall for your EC2 instance. It defines exactly which ports are open and who can connect.

In this step, get ready to:
  • Define a security group that allows HTTP and SSH inbound traffic.
  • Add an outbound rule so your instance can send traffic to the internet.
  • Preview your full infrastructure plan with terraform plan.
Define the web server security group

A security group controls which traffic can enter and leave your resources. You need to allow HTTP traffic (so browsers can reach your web server) and SSH traffic (so you can connect to administer the instance).

  • In VS Code, create a new file called security.tf inside your aws-vpc-project folder.
  • Add the full security group resource with inbound and outbound rules by pasting this code:
resource "aws_security_group" "web_sg" {
  # Name and description for the security group
  name        = "web-server-sg"
  description = "Allow HTTP and SSH inbound traffic"
  vpc_id      = aws_vpc.main.id

  # Allow web browsers to reach port 80
  ingress {
    description = "HTTP from anywhere"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  # Allow SSH connections on port 22
  ingress {
    description = "SSH from anywhere"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  # Allow all outbound traffic
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "web-server-sg"
  }
}

What does this code do?

This resource creates a firewall attached to your VPC that controls both inbound and outbound traffic:

  • vpc_id = aws_vpc.main.id attaches this security group to the VPC you created in the previous step.
  • The first ingress block opens port 80 (HTTP) so web browsers can access your server.
  • The second ingress block opens port 22 (SSH) so you can remotely connect to the instance.
  • cidr_blocks = ["0.0.0.0/0"] means "from any IP address on the internet."
  • The egress block with protocol = "-1" allows all outbound traffic on all ports. Without this, your instance could not send responses back to browsers or download updates.
  • Save security.tf.

You won't see a visible change yet. The next substep uses terraform plan to confirm everything is wired up correctly.

Seeing a syntax error?

Make sure every opening { has a matching closing }. Each ingress and egress block must be fully contained inside the aws_security_group resource (before the final closing brace).

Help me fix my security.tf syntax error

✔️ Awesome, I've got everything!

Great. Double check you've saved security.tf before moving on.

ⓧ I'd like to double check the full code

Here is the complete security.tf file:

resource "aws_security_group" "web_sg" {
  name        = "web-server-sg"
  description = "Allow HTTP and SSH inbound traffic"
  vpc_id      = aws_vpc.main.id

  ingress {
    description = "HTTP from anywhere"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "SSH from anywhere"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "web-server-sg"
  }
}
Preview your infrastructure with terraform plan

Before deploying anything, Terraform lets you preview exactly what it will create. This is your chance to catch mistakes before they cost time or money.

  • Run the plan command in your VS Code terminal:
terraform plan
  • Scroll through the output and confirm you see entries for your VPC, public subnet, private subnet, internet gateway, route table, route table association, and security group.
  • At the bottom, you should see a summary line showing Plan: 7 to add, 0 to change, 0 to destroy.

What does terraform plan do?

The terraform plan command compares your configuration files against the current state (which is empty, since nothing is deployed yet). It calculates what actions are needed to make reality match your code.

This is a dry run. Nothing is created or changed on AWS. You are just previewing the execution plan.

Seeing an error instead of a plan?

  • If you see a provider error, make sure you ran terraform init successfully in a previous step. Run it again if needed.
  • If you see a syntax error pointing to a specific file and line, open that file and check for missing braces or typos near that line number.
  • If you see a reference error like "resource not found," make sure the resource names in your references (like aws_vpc.main.id) match the resource labels you defined.

Help me debug my terraform plan error

Your security group is defined and your full infrastructure plan looks good. Next up, you will deploy an EC2 instance into your public subnet and see your web server go live.

Deploy an EC2 Instance

Your network is built and your security rules are locked in. Now it's time to put a server inside that network and make it serve a real web page.

In this step, you'll define an EC2 instance that automatically installs a web server when it launches. Then you'll deploy everything to AWS with a single command.

In this step, get ready to:
  • Look up the latest Amazon Linux AMI for your instance.
  • Define an EC2 instance with a startup script that installs a web server.
  • Deploy all your infrastructure to AWS with Terraform.
Look up the latest Amazon Linux AMI

Every EC2 instance needs an AMI (Amazon Machine Image) as its base operating system. Instead of hardcoding a specific AMI ID that changes over time, you'll use a data source to automatically find the latest one.

  • Create a new file called compute.tf in your aws-vpc-project folder.
  • Add the AMI data source by pasting this code:
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

What does this code do?

A data block in Terraform reads information from AWS without creating anything. This one searches for the most recent Amazon Linux 2023 AMI.

  • most_recent = true tells Terraform to pick the newest AMI that matches your filters.
  • owners = ["amazon"] ensures you only get official Amazon-published images.
  • The two filter blocks narrow results to Amazon Linux 2023 images using hardware virtualization (hvm).
  • Save compute.tf.

You won't see a change yet. The next chunk adds the EC2 instance that uses this AMI.

Define the web server instance

Now you'll define the actual EC2 instance. This resource ties together everything you've built so far: the public subnet, the security group, and the AMI you just looked up.

  • In compute.tf, add the following code below the data source you just created:
resource "aws_instance" "web_server" {
  ami                    = data.aws_ami.amazon_linux.id
  instance_type          = "t3.micro"
  subnet_id              = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.web_sg.id]

  # Startup script that installs and starts a web server
  user_data = <<-EOF
              #!/bin/bash
              dnf update -y
              dnf install -y httpd
              systemctl start httpd
              systemctl enable httpd
              echo "<h1>Hello from my Terraform-deployed server!</h1><p>VPC: internship-vpc | Subnet: public-subnet</p>" > /var/www/html/index.html
              EOF

  tags = {
    Name = "web-server"
  }
}

What does this code do?

  • ami references the AMI data source you defined above, so Terraform always uses the latest Amazon Linux 2023 image.
  • instance_type = "t3.micro" sets a small instance size covered by your AWS Free Tier credits.
  • subnet_id places the instance in your public subnet so it gets a public IP address.
  • vpc_security_group_ids attaches the security group you created, allowing HTTP and SSH traffic.
  • user_data is a startup script that runs automatically when the instance first boots. It installs Apache (httpd), starts the web server, and writes a custom HTML page.
  • Save compute.tf.

You won't see a change yet. The next substep creates outputs and deploys everything.

✔️ Awesome, I've got everything!

Great. Make sure you've saved compute.tf before continuing.

ⓧ I'd like to double check the full code

data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

resource "aws_instance" "web_server" {
  ami                    = data.aws_ami.amazon_linux.id
  instance_type          = "t3.micro"
  subnet_id              = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.web_sg.id]

  user_data = <<-EOF
              #!/bin/bash
              dnf update -y
              dnf install -y httpd
              systemctl start httpd
              systemctl enable httpd
              echo "<h1>Hello from my Terraform-deployed server!</h1><p>VPC: internship-vpc | Subnet: public-subnet</p>" > /var/www/html/index.html
              EOF

  tags = {
    Name = "web-server"
  }
}
Create outputs and deploy your infrastructure

Terraform can output useful values after it creates resources. You'll output the public IP address of your web server so you can visit it in a browser.

  • Create a new file called outputs.tf in your aws-vpc-project folder.
  • Add the following output definitions:
output "web_server_public_ip" {
  description = "Public IP address of the web server"
  value       = aws_instance.web_server.public_ip
}

output "vpc_id" {
  description = "ID of the VPC"
  value       = aws_vpc.main.id
}

What does this code do?

Output blocks tell Terraform to display specific values after a successful apply.

  • web_server_public_ip gives you the IP address you'll paste into your browser to see your web page.
  • vpc_id shows the unique identifier AWS assigned to your VPC, useful for referencing it later.
  • Save outputs.tf.

✔️ Awesome, I've got everything!

Great. Make sure you've saved outputs.tf before continuing.

ⓧ I'd like to double check the full code

output "web_server_public_ip" {
  description = "Public IP address of the web server"
  value       = aws_instance.web_server.public_ip
}

output "vpc_id" {
  description = "ID of the VPC"
  value       = aws_vpc.main.id
}

Now it's time to deploy. This single command creates your VPC, subnets, internet gateway, route table, security group, and EC2 instance all at once.

  • Deploy your entire infrastructure by running this command in the VS Code terminal:
terraform apply

What will Terraform ask you?

Terraform displays a plan of all the resources it will create, then asks: "Do you want to perform these actions?" Only yes will be accepted to approve. You cannot use y or other shortcuts.

  • Review the plan output. You should see multiple resources listed (VPC, subnets, internet gateway, route table, security group, EC2 instance).
  • Type yes and press Enter to confirm.
  • Wait for Terraform to finish creating all resources. This takes 1-2 minutes.

When deployment completes, you should see a message like "Apply complete! Resources: 7 added, 0 changed, 0 destroyed." followed by your outputs, including the public IP address of your web server.

Seeing an error during apply?

  • If you see an authentication error, run aws configure again and double-check your Access Key ID and Secret Access Key.
  • If you see a "limit exceeded" error, your AWS account may not be fully activated yet. Wait a few minutes and try again.
  • If you see a syntax error, compare your compute.tf against the full code in the tab above.

Help me debug my terraform apply error.

Your infrastructure is live on AWS. Next up, you'll open that public IP in your browser and verify the web server is serving your custom page.

Verify Your Infrastructure

Your infrastructure is live on AWS. Every resource you defined in code now exists in the real world.

In this step, you will confirm everything works end-to-end. You will test your web server from a browser, explore your resources in the AWS Console, and learn how to audit your Terraform state.

In this step, get ready to:
  • Access your web server from the browser using its public IP.
  • Review your infrastructure in the AWS Console.
  • Audit your Terraform state from the command line.
Access your web server in the browser

The terraform apply output from the previous step printed your EC2 instance's public IP address. You will use this to confirm the web server is responding to HTTP requests.

  • Find the web_server_public_ip value in your terminal output from terraform apply.
  • Open a new browser tab.
  • Paste http:// followed by the IP address into the address bar and press Enter.

Why HTTP and not HTTPS?

Your security group only allows inbound traffic on port 80 (HTTP). HTTPS requires a TLS certificate and port 443, which you did not configure in this project.

You should see a page that says "Hello from my Terraform-deployed server!" with your VPC and subnet names displayed below.

Page not loading?

  • Make sure you are using http:// and not https:// in the URL.
  • Wait 2-3 minutes after terraform apply finishes. The EC2 instance needs time to install and start httpd via the user_data script.
  • Confirm the IP address matches what Terraform printed. You can re-check by running terraform output web_server_public_ip in your terminal.

Still stuck? Help me troubleshoot why my EC2 web server page is not loading in the browser.

View your infrastructure in the AWS Console

The AWS Console gives you a visual overview of everything Terraform created. This is how you confirm that what you defined in code matches what exists in the cloud.

  • Switch to the browser tab where you are signed into the AWS Management Console.
  • In the search bar at the top, type VPC and click the VPC service result.
  • Click Your VPCs in the left sidebar.
  • Confirm you see internship-vpc in the list with CIDR block 10.0.0.0/16.
  • Click Subnets in the left sidebar.
  • Confirm you see both public-subnet and private-subnet listed.

Now check your running instance.

  • In the search bar at the top, type EC2 and click the EC2 service result.
  • Click Instances (running) on the EC2 dashboard.
  • Confirm you see your web-server instance with a Running state.

What am I looking at?

Everything you see in these dashboards was created by your Terraform code. The VPC, subnets, internet gateway, route tables, security groups, and EC2 instance all exist because of the .tf files in your project folder.

If you ever deleted a resource manually from this console, Terraform would detect the drift on the next terraform plan and offer to recreate it.

Audit your Terraform state

Terraform tracks every resource it manages in a state file. Cloud engineers use state commands to audit what Terraform controls and inspect resource details without opening the AWS Console.

  • Return to the VS Code terminal.
  • List all resources Terraform manages by running this command:
terraform state list

You should see a list of resource addresses, one per line. This includes data.aws_ami.amazon_linux, aws_instance.web_server, aws_vpc.main, and all your other resources.

What does this command do?

The terraform state list command shows the resource addresses for every resource Terraform knows about in a configuration. Each line maps directly to a resource or data block in your .tf files.

  • View the full state of your infrastructure by running this command:
terraform show

You should see detailed, human-readable output showing every attribute of every resource. This includes IDs assigned by AWS, timestamps, and configuration values.

What does terraform show display?

The terraform show command provides human-readable output from a state or plan file. It lets you inspect the current state as Terraform sees it.

This is how cloud engineers audit what Terraform manages without logging into the console. It gives you a single-pane view of your entire infrastructure, including values that AWS generated like unique IDs and ARNs.

Seeing an empty or error output?

  • Make sure you are running the command from inside the aws-vpc-project folder where your .tf files live.
  • Confirm that terraform apply completed successfully in the previous step. If it failed, the state file may be incomplete.

Need more help? Help me troubleshoot why terraform state list or terraform show is not displaying my resources.

Your entire cloud infrastructure is verified and running. Next up, you will take on the Secret Mission and extend your network with a private instance and NAT Gateway.

Secret mission

Add a Private Instance with NAT Gateway

Your public web server is live, but real cloud architectures don't put everything on the public internet. Deploy a second EC2 instance in the private subnet and configure a NAT Gateway so it can download updates while remaining completely invisible to the outside world.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. Your EC2 instances and public IPv4 address are covered by Free Tier credits, but the NAT Gateway is not and costs approximately $0.045/hour while active.

Cost warning

The NAT Gateway you created in the Secret Mission costs approximately $0.045/hour (about $32/month) and is NOT covered by AWS Free Tier. If you completed the Secret Mission, run terraform destroy as soon as you are done testing to avoid unexpected charges.

Resources you used:

  • 2 EC2 instances (t3.micro) in us-east-1, covered by Free Tier credits.
  • 1 VPC with subnets, internet gateway, route tables, and security groups (VPC itself is free).
  • 1 NAT Gateway with Elastic IP (NOT covered by Free Tier).
  • 1 public IPv4 address (covered by Free Tier for 750 hours/month).
  • Local aws-vpc-project folder on your Desktop.

Keep everything running

No action needed. Choose this if you are still actively building or plan to extend this project soon.

  • Your EC2 instances use Free Tier credits ($100 sign-up credit for 6 months from account creation).
  • Monitor your usage in the AWS Billing dashboard to stay within Free Tier limits.
  • The NAT Gateway costs approximately $0.045/hour even when no traffic flows through it. Consider destroying it and recreating later with terraform apply.
  • AWS CLI and Terraform remain installed on your machine.

Pause - I'll come back to this later

Stop running instances to pause compute charges while keeping your infrastructure defined in Terraform.

  • In the AWS Console, navigate to EC2 then Instances.
  • Select both instances (web-server and private-server).
  • Click Instance state, then Stop instance.
  • The NAT Gateway continues to incur charges even with stopped instances. To pause costs fully, run terraform destroy instead and recreate everything later with terraform apply.

Delete - I don't want to use this again

Remove all AWS resources Terraform created and delete your local project files. You can always recreate everything instantly with terraform apply in the future.

  • In your VS Code terminal, make sure you are inside the aws-vpc-project folder.
  • Destroy all AWS resources by running:
terraform destroy
  • Terraform will list every resource it plans to delete and ask for confirmation. Type yes and press Enter.
  • Wait for Terraform to finish. You should see "Destroy complete!" with the count of destroyed resources.
  • Delete the local project folder. On macOS/Linux, run:
cd ~/Desktop
rm -rf aws-vpc-project
  • On Windows, run:
cd ~\Desktop
rmdir /s /q aws-vpc-project
  • Verify the folder is gone by running:
ls
  • You should no longer see aws-vpc-project in the output.

Nice Work!

Nice Work!

Nice work! You just built real cloud infrastructure the way professional cloud engineers do.

You've learned how to:

  • Built a custom VPC with public and private subnets, an Internet Gateway, and Route Tables from scratch.
  • Secured your infrastructure with Security Groups that control exactly which traffic is allowed in and out.
  • Deployed a running web server and managed your entire infrastructure lifecycle with Terraform (init, plan, apply, destroy).
  • Added a NAT Gateway for secure private subnet internet access, keeping your private instance completely unreachable from the public internet.

You now have hands-on experience with the most-requested skills in cloud engineering internships: AWS, Terraform, and VPC networking.

Ready to quiz yourself?