Harden an AWS VPC Web Server
Build a segmented AWS VPC and secure an Apache web server with minimal access.
Introduction
30 Second Summary
When a page refuses to load, the browser rarely explains whether the server is broken or a security control is doing its job. Good troubleshooting turns that uncertainty into visible proof.
In this project, you will build a segmented network in AWS around a live Apache HTTP Server. You will use a deliberate failure to prove that the network blocks unwanted traffic before allowing only the web connection your page needs.
What You'll Build
You will load a public URL that displays a hostname from inside your cloud network after your evidence proves the same request was blocked by default.
By the end of this project, you'll have:
- Load a live web demo from an Amazon EC2 instance inside your own Amazon VPC.
- Explain the routing boundary by showing that the public subnet reaches an internet gateway while the private subnet has no direct internet route.
- Present security evidence in a public GitHub case study where reviewers can see a rendered Mermaid architecture diagram beside the before-and-after test evidence.
- Secret Mission: Restrict HTTP access to your current public IPv4 address. Prove that your Windows computer remains allowed. Confirm that a second network stays blocked.
Are there any prerequisites?
You need a Windows computer with Visual Studio Code already installed plus access to your existing GitHub account. Internet access, an email address, and a phone that can receive verification messages cover the AWS signup step.
Before We Start
First, lock in what you are building and why it matters for your master's application. This gives every technical decision in the lab a clear purpose.
Set Up a Cost-Safe AWS Workspace
Your lab cannot prove cloud controls until it has an AWS workspace. Metered resources can consume credits as soon as they exist.
This step puts cost safeguards in place before any metered resource launches. You also create the portfolio home that later steps fill with security evidence.
In this step, get ready to:
- Activate an AWS Free plan account with a billing budget.
- Create a public GitHub repository with a starter README.
- Prepare the matching local folder in Visual Studio Code.
Create your AWS account and budget
The AWS Free plan provides access to the console without out-of-pocket charges unless you upgrade or activate paid-only services. A billing budget gives you a cost checkpoint before the lab creates cloud resources.
Signup may request payment information for identity verification. AWS may place a temporary USD $1 or equivalent hold for 3-5 days.
- Visit the AWS Free page to begin account creation.
- Select Create account.
- Select Sign up for AWS (new).
- Choose the option that uses your existing GitHub login.
- Complete the email verification.
- Complete the contact verification.
- Select the Free plan.
- Finish any additional identity verification that AWS requests.
Extra identity verification can extend signup beyond the active project time. A longer pause here is normal for accounts selected for review.
- Continue to AWS Management Console Home after activation completes.
- Check the page for your Free plan or Free Tier credit status.
Your cloud workspace is active. The budget now adds a visible cost safeguard before you launch anything metered.
The simplified Zero spend budget template sends billing notifications to the email address you provide. This keeps your cost status visible throughout the lab.
- Enter Billing and Cost Management in the search bar at the top of the console.
- Select Billing and Cost Management from the results.
- Select Budgets from the navigation.
- Select Create budget.
- Select Use a template (simplified).
- Select the Zero spend budget template.
- Enter your email address in the notification email field.
- Select Create budget.
You should see Zero spend budget in the Budgets list. The notification recipient uses the email address you entered.
What protects your spend?
The Free plan prevents charges unless the account moves to a Paid plan or activates paid-only services. Your budget adds a billing checkpoint tied to your email address.
Together, these safeguards make cost status visible before the lab creates an instance or public IPv4 address.
Budget not listed?
- Refresh the Budgets page after the creation request finishes.
- Check that you selected the simplified template named Zero spend budget.
Help me diagnose why my AWS Zero spend budget does not appear after I created it.
Create your public GitHub repository
A public repository gives reviewers one place to inspect your architecture and security evidence. Enabling a starter README makes the repository visible as a documented project from the beginning.
- Return to GitHub in your browser.
- Select New repository.
- Enter aws-vpc-security-lab as the repository name.
- Choose Public as the visibility.
- Enable Add README.
- Select Create repository.
Your repository page should show aws-vpc-security-lab with public visibility. The rendered starter README should display the repository name as its heading.
Repository not created?
- Check that the repository name is exactly aws-vpc-security-lab.
- Confirm that Public is selected before trying again.
Help me troubleshoot creating my public GitHub repository.
Prepare the local project folder
The local folder mirrors the repository name so each file has an obvious destination later. It starts with one Markdown heading plus empty locations for the bootstrap script and redacted screenshots.
- Press the Windows key to open search.
- Type Visual Studio Code into the search bar.
You should see Visual Studio Code in the search results.
- Press Enter to open Visual Studio Code.
- Click File in the top menu bar.
The File menu should now be open.
- Click Open Folder.
- Select Desktop in the folder picker.
The folder picker should now show the contents of your Desktop.
- Click New folder.
- Enter aws-vpc-security-lab as the folder name.
You should see the new aws-vpc-security-lab folder on your Desktop.
- Select the aws-vpc-security-lab folder.
- Click Select Folder.
The Explorer sidebar should show Desktop\aws-vpc-security-lab as the open folder.
- Click the New File icon in the Explorer sidebar.
- Enter README.md as the file name.
The new README.md file should open in the editor.
- Type # aws-vpc-security-lab on the first line.
- Press Enter twice after the heading.
The editor should show the project heading followed by a blank line.
- Press Ctrl+S to save README.md.
- Click the New File icon in the Explorer sidebar.
The saved README should remain listed in the Explorer sidebar.
- Enter user-data.sh as the file name.
- Leave user-data.sh empty.
The empty user-data.sh file should now appear beside README.md.
- Click the New Folder icon in the Explorer sidebar.
- Enter evidence as the folder name.
The empty evidence folder should appear in the Explorer sidebar. It will hold redacted VPC resource-map and HTTP test screenshots.
✔️ Awesome, I've got everything!
Your local folder has the exact starter structure needed for the next steps.
ⓧ I'd like to double check the full code
The README.md file contains exactly # aws-vpc-security-lab followed by a blank line.
The user-data.sh file is empty.
The evidence folder is empty and ready for redacted screenshots.
Before the final check, do you expect all three workspaces to use the same project name? The next check reveals the answer.
- Return to AWS Management Console Home from earlier.
- Read the account's plan or Free Tier credit status.
- Return to Billing and Cost Management.
- Select Budgets.
You should see the Free plan or Free Tier credit status for the account. You should also see Zero spend budget in the Budgets list.
- Return to the aws-vpc-security-lab repository in GitHub.
- Read the repository visibility.
- Read the heading in the starter README.
- Switch back to Visual Studio Code from earlier.
- Inspect the files in the Explorer sidebar.
You should see a public GitHub repository with its starter README. Your local folder should contain README.md, an empty user-data.sh file, and an empty evidence folder.
Your cost controls and portfolio workspace are ready. Next, you'll build the segmented VPC that gives the lab its public and private network boundaries.
Build the Segmented VPC
Your cost safeguards and portfolio workspace are ready in the AWS Management Console. You can now build the network that the later web server depends on.
A default network hides the routing decisions that distinguish a public tier from a private tier. In this step, you'll create an Amazon VPC whose boundaries are visible in its resource map.
In this step, get ready to:
- Create the masters-security-lab VPC with one Availability Zone.
- Segment its address space into a public subnet and a private subnet.
- Capture the routing boundary in a redacted resource-map screenshot.
Create the VPC foundation
Each VPC owns a private address space. CIDR addressing defines the size of that space through a range such as 10.0.0.0/16.
- Use the search bar at the top of the console to search for VPC.
- Select VPC from the results.
You'll see the Amazon VPC dashboard. The Create VPC control is available there.
- Click Create VPC.
- Select VPC and more.
You'll see the VPC wizard with a Preview panel. The preview updates as you define the network.
- Enter masters-security-lab under Name tag auto-generation.
- Enter 10.0.0.0/16 under IPv4 CIDR block.
The preview now identifies the VPC as masters-security-lab. Its address space shows 10.0.0.0/16.
- Keep Default tenancy selected.
- Set the Availability Zone count to 1.
The preview now places the network in one Availability Zone. This keeps the lab focused on routing boundaries.
Define the subnet boundaries
A subnet divides the VPC address space into a smaller range. A route table controls where traffic from that range can travel.
A NAT gateway can provide outbound internet access for private resources. This lab leaves that service out so the private routing boundary stays visible.
- Set the public subnet count to 1.
- Set the private subnet count to 1.
The preview now shows two subnet ranges inside the VPC. One range belongs to each network tier.
- Expand Customize subnets CIDR blocks.
- Set the public subnet CIDR to 10.0.1.0/24.
The public subnet now occupies 10.0.1.0/24 inside the larger VPC range.
- Set the private subnet CIDR to 10.0.2.0/24.
- Set NAT gateways to None.
The preview now shows distinct subnet ranges. It shows no NAT gateway.
- Set VPC endpoints to None.
- Keep the default DNS options enabled.
The next click creates every network resource shown in the preview. Your Zero spend budget remains active.
- Review the Preview panel.
You should see one VPC with two subnets. The plan should include route tables plus an internet gateway with no NAT gateway.
- Create the network by clicking Create VPC.
You'll see the creation workflow complete. That puts your network foundation in place.
VPC creation not completing?
- Confirm that 10.0.1.0/24 sits inside 10.0.0.0/16.
- Confirm that 10.0.2.0/24 does not overlap the public subnet range.
- Check that both subnet counts are set to 1.
Help me diagnose why my VPC wizard cannot create this network.
Verify the routing boundaries
The Resource map shows how the subnets connect to their route tables. It also shows the attached internet gateway.
- Select masters-security-lab from the completed creation workflow.
- Choose the Resource map tab.
You'll see one public subnet with its route table. You'll also see one private subnet with a separate route table.
Before you inspect the route tables, which subnet do you expect to have a direct path to the internet gateway?
- Open the route table link connected to the public subnet.
- Select Routes in the route table details.
You'll see that the public route table includes a route to the attached internet gateway. This route makes the subnet public.
- Return to the Resource map tab.
- Open the route table link connected to the private subnet.
The selected resource now belongs to the private subnet. Its details let you test the other side of the boundary.
- Select Routes in the private route table details.
You'll see no direct route from the private route table to the internet gateway. That is the segmented boundary you set out to prove.
Routes do not match?
- Check the subnet association before changing any routes.
- Confirm that you inspected two separate route tables.
- Return to the resource map to verify which route table connects to each subnet.
Help me identify why my public and private subnet routes look the same.
Cleaning a console screenshot is a little fiddly because the surrounding page can expose account-specific identifiers. The evidence only needs the architecture.
- Return to the Resource map tab.
- Capture only the visible map area with your Windows screenshot tool.
Your captured image should include both subnets. It should also include their route tables plus the internet gateway.
- Review the captured image for account-specific identifiers.
- Redact every account-specific identifier that appears.
The redacted image should preserve the network relationships. It should show no NAT gateway.
- Save the image inside the existing evidence folder as vpc-resource-map.png.
The evidence now exists at evidence/vpc-resource-map.png inside your local project folder.
- Switch back to Visual Studio Code.
- Expand the evidence folder in the Explorer sidebar.
You'll see vpc-resource-map.png inside the folder. The image proves that your segmented VPC exists.
Your VPC now has a visible public path plus a private boundary with no direct internet route. Next, you'll launch the web server into the public subnet and watch its empty inbound policy block the first request.
Launch a Blocked Web Server
Your segmented Amazon VPC now has visible public and private routing boundaries. The next goal is to place a real web workload in the routed public subnet.
An Amazon EC2 instance runs the server. User data bootstraps Apache HTTP Server during the first boot.
A new security group starts with no inbound rules. You will test that secure default from your browser before changing any access.
In this step, get ready to:
- Create a security group with no inbound rules.
- Launch an Amazon EC2 instance that bootstraps Apache HTTP Server from user data.
- Test the HTTP boundary from your browser.
Create the empty security group
The security group is the firewall attached directly to your instance. Its initial rule set gives you a secure baseline for the connectivity test.
- In the Amazon VPC console from earlier, choose Security groups in the left navigation.
- Choose Create security group.
- Enter web-lab-sg for Security group name.
- Describe its purpose in Description as protecting the lab web server.
- Select masters-security-lab for VPC.
- Leave Inbound rules empty.
- Keep the default outbound rule that allows all traffic.
- Choose Create security group.
What does the empty rule set do?
A new security group accepts no inbound connections. Its default outbound rule still lets the instance request packages during boot.
Security groups are stateful. Responses to permitted traffic can return automatically.
- Select web-lab-sg from the security group list.
- Open the Inbound rules tab.
- Confirm that no inbound rules are listed.
- Open the Outbound rules tab.
- Confirm that the default allow-all outbound rule remains listed.
Good. web-lab-sg now provides the controlled firewall boundary for your test.
Prepare the server launch
The local user-data.sh file is currently empty. The script you add runs as root when the instance boots for the first time.
- Switch back to Visual Studio Code from earlier.
- Select user-data.sh in the Explorer sidebar.
- Fill user-data.sh with the bootstrap script by copying the code below:
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo " Hello from $(hostname -f) in the public subnet " > /var/www/html/index.html
What does this script do?
- The #!/bin/bash line tells the instance to run the file as a Bash script.
- The yum update -y command refreshes the installed packages without interactive prompts.
- The yum install -y httpd command installs Apache HTTP Server.
- The systemctl start httpd command starts the web server during this boot.
- The systemctl enable httpd command configures the service to start after future reboots.
- The final line writes the hostname message to /var/www/html/index.html.
- Save user-data.sh.
- Confirm that the first line is #!/bin/bash.
- Confirm that the final line writes to /var/www/html/index.html.
Does the bootstrap file look different?
- Check that user-data.sh contains all six lines in the same order.
- Check that the hostname expression remains $(hostname -f).
- Check that the output path ends with index.html.
Ask for help comparing your file with the required bootstrap script.
✔️ Awesome, I've got everything!
Your bootstrap file is saved. It is ready for the EC2 launch wizard.
ⓧ I'd like to double check the full code
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo " Hello from $(hostname -f) in the public subnet " > /var/www/html/index.html
The bootstrap file is ready. The launch wizard now combines it with the network boundary you created.
- In the AWS Management Console search bar, enter EC2.
- Select Amazon EC2 from the search results.
- Choose Launch instance.
- Enter vpc-web-lab for Name.
- Select Amazon Linux 2023 AMI release 2023.12.20260930 under Application and OS Images.
- Select t3.micro under Instance type.
- Select Proceed without a key pair (not recommended) under Key pair (login).
Why proceed without a key pair?
A key pair supports SSH login to an instance. This lab uses user data for server configuration.
The security group contains no SSH rule. Administrative access stays outside the public attack surface.
- Choose Edit under Network settings.
- Select masters-security-lab for VPC.
- Select the public subnet with CIDR 10.0.1.0/24 for Subnet.
- Select Enable for Auto-assign public IP.
- Select Select existing security group under Firewall.
- Select web-lab-sg as the existing security group.
How does traffic reach the server?
The public subnet already routes internet-bound traffic through the attached internet gateway. The auto-assigned public IPv4 address gives the instance an internet-facing destination.
The security group remains the final inbound boundary. Only its rules decide which incoming connections can enter.
- Expand Advanced details.
- Select Standard for Credit specification.
- Paste the complete contents of user-data.sh into User data.
- Leave the base64 checkbox empty.
Launching this instance starts using your included AWS credits. Your Zero spend budget is already watching the account.
- Choose Launch instance.
- Return to the EC2 instance list from the launch confirmation.
Prove the inbound block
The instance can enter the Running state before the first boot finishes. Both status checks must pass before you test its browser address.
Expect the first boot to take a few minutes during the Apache HTTP Server installation.
- Find vpc-web-lab in the instance list.
- Wait until its Instance state shows Running.
- Wait until both EC2 status checks report that they have passed.
- Select the vpc-web-lab instance.
- Find the Public IPv4 address field in the instance details.
- Record the address here: your public IPv4 address.
That is the first boot complete. Your instance is running inside the public subnet with its bootstrap script applied.
Are the status checks still waiting?
- Refresh the instance list after another minute.
- Verify that the selected subnet has CIDR 10.0.1.0/24.
- Verify that the pasted user data begins with #!/bin/bash.
Ask for help diagnosing an EC2 instance whose status checks are not passing.
Before you test the address, do you expect the browser request to reach the web server with the current inbound rules?
- Open a new tab in the browser that contains your AWS console.
- Enter http:// followed by your public IPv4 address in the address bar.
- Press Enter.
Your browser waits before reporting that the site cannot be reached. A timeout is also an expected result.
Why is the timeout intentional?
The public route gives the subnet a path through the internet gateway. The public IPv4 address identifies the instance on that path.
The web-lab-sg security group has no inbound rules. The browser request therefore has no permitted entry path.
This controlled failure proves that the firewall boundary is active before you grant web access.
- Capture a cropped view of the failed browser result that excludes the temporary public IPv4 address.
- Save the screenshot as evidence/blocked-http.png inside the local aws-vpc-security-lab folder.
- Switch back to Visual Studio Code from earlier.
- Confirm that evidence/blocked-http.png appears in the Explorer sidebar.
You now have evidence of the intended failure. The screenshot records a running server protected by an empty inbound rule set.
- Return to the Amazon EC2 console tab.
- Select the vpc-web-lab instance.
- Open the Security tab in the instance details.
- Select web-lab-sg.
- Confirm that the inbound rules table remains empty.
The empty table matches the browser result. Your running web server has no inbound HTTP permission.
Your server is running behind a proven inbound block. Next, you will add one focused web rule and test the same address again.
Allow Required Web Traffic
During the last step, your Amazon EC2 instance stayed healthy while the browser request failed. The empty inbound policy on the security group stopped the request before it reached Apache HTTP Server.
Now you'll permit HTTP through TCP port 80. You will then prove the page works while SSH remains closed.
In this step, get ready to:
- Add one inbound HTTP rule to web-lab-sg.
- Prove the Apache page loads through the public IPv4 address.
- Audit the public subnet and private subnet routing boundaries.
Allow inbound HTTP traffic
An inbound rule defines which traffic may initiate a connection to your instance. This server only needs browser requests sent over HTTP.
- Return to the AWS Management Console from the previous step.
- Select Security groups from the Amazon EC2 console navigation.
- Select web-lab-sg.
- Choose Edit inbound rules.
- Add one inbound rule.
- Set Type to HTTP.
- Set Source to Anywhere-IPv4.
- Choose Save rules.
That is the firewall change done. The inbound rules now show TCP port 80 from 0.0.0.0/0.
Why does this rule work?
The HTTP type maps to TCP port 80. Anywhere-IPv4 maps to 0.0.0.0/0.
Security groups are stateful. Response traffic for an allowed request can return to your browser.
No SSH rule exists. TCP port 22 therefore remains closed to inbound traffic.
Prove the web path works
The firewall rule is the only part of the request path that changed. Repeating the same browser request isolates the effect of that rule.
Before you refresh, do you expect the same URL to time out or reach Apache now?
- Return to the browser tab containing http://PUBLIC_IPV4_ADDRESS.
- Refresh the page.
The page displays Hello from HOSTNAME in the public subnet. The hostname identifies the instance serving the response.
That is the breakthrough: your browser can now reach Apache through the application port you allowed.
Page still blocked?
- Confirm that web-lab-sg contains an inbound HTTP rule from 0.0.0.0/0.
- Confirm that web-lab-sg remains attached to vpc-web-lab.
- Check that the browser address begins with http://.
Help me diagnose why my Apache page remains unreachable after adding the HTTP rule.
- Capture only the page content using the screenshot method from your earlier evidence.
- Save the captured image as evidence/allowed-http.png inside your local project folder.
- Switch back to the Explorer sidebar in Visual Studio Code.
- Confirm that allowed-http.png is listed inside evidence.
Verify the complete traffic path
A loaded page proves HTTP works from the browser to the server. A complete security review also checks that administrative access remains closed.
The Amazon VPC route tables explain why the public subnet can receive internet traffic while the private subnet remains isolated.
Before this final review, do you expect to find any inbound path other than HTTP exposed to the internet?
- Return to web-lab-sg in the Amazon EC2 console.
- Confirm that its only inbound rule shows HTTP, TCP, 80, and 0.0.0.0/0.
- Confirm that no inbound rule lists SSH or port 22.
- Confirm that the default outbound allow-all rule remains.
- Return to the masters-security-lab public route table in the Amazon VPC console.
- Confirm that it still includes a route to the attached internet gateway.
- Return to the masters-security-lab private route table.
- Confirm that it has no direct route to the internet gateway.
Your review should show one inbound HTTP rule on TCP port 80 from 0.0.0.0/0. No SSH or port 22 rule appears.
The public route table still points internet-bound traffic to the internet gateway. The private route table has no direct route there.
Your controlled failure now has a clear before-and-after result. Next, you'll publish this proof in GitHub so another person can review the design.
Publish Security Evidence
Your VPC now separates public traffic from private resources. Your Apache HTTP Server page proves that the security group allows the web path you added.
A reviewer needs enough evidence to understand the architecture. In this step, you'll turn your AWS work into a public GitHub case study using Markdown, a Mermaid diagram, test screenshots, and security decisions.
In this step, get ready to:
- Document your architecture and security tests in README.md.
- Remove sensitive details from your script and evidence images.
- Publish the completed case study to GitHub.
Build the portfolio README
The starter README.md only contains the repository name. The finished document explains what you built before showing the evidence that supports each claim.
- Switch back to the aws-vpc-security-lab folder in Visual Studio Code.
- Select README.md from the file tree.
- Replace the starter heading by pasting this opening content:
# Hardened AWS VPC Web Lab
A cloud networking and cybersecurity lab that demonstrates network segmentation, routing, least-privilege ingress, controlled failure, and evidence-based troubleshooting in AWS.
## Project outcome
I designed a custom Amazon VPC, launched an Apache web server in its public subnet, proved that an empty inbound rule set blocked the application, and restored only the required HTTP path without exposing SSH.
## Architecture
```mermaid
graph TD;
Internet-->InternetGateway;
InternetGateway-->PublicRouteTable;
PublicRouteTable-->PublicSubnet;
PublicSubnet-->EC2WebServer;
SecurityGroup-->EC2WebServer;
VPC-->PublicSubnet;
VPC-->PrivateSubnet;
```
How does this opening help?
- The project outcome states the security result in language a reviewer can scan quickly.
- The Mermaid graph shows how internet traffic reaches the web server.
- The private subnet appears in the diagram without an internet-facing connection.
- Save README.md.
- Open the Markdown preview by pressing Ctrl+Shift+V.
- Confirm that the preview shows the Hardened AWS VPC Web Lab heading.
- Confirm that the preview renders the architecture as connected boxes.
Diagram not rendering?
Check that the opening fence contains mermaid. Confirm that the closing fence remains on its own line.
Ask for help with the diagram:
- Return to the editor view by pressing Ctrl+Shift+V.
- Append the addressing plan and security policy by pasting this content below the architecture section:
## Addressing plan
| Component | CIDR | Purpose |
| --- | --- | --- |
| VPC | `10.0.0.0/16` | Overall lab address space |
| Public subnet | `10.0.1.0/24` | Internet-routed web tier |
| Private subnet | `10.0.2.0/24` | Isolated future application or data tier |
## Security policy
| Direction | Protocol | Port | Source or destination | Reason |
| --- | --- | --- | --- | --- |
| Inbound | TCP | 80 | `0.0.0.0/0` | Permit the public HTTP demonstration |
| Inbound | TCP | 22 | Not allowed | Avoid exposing SSH administration |
| Outbound | All | All | `0.0.0.0/0` | Allow initial package installation and responses |
What do these tables prove?
The addressing plan records the CIDR boundary for each network segment. It makes the public and private roles explicit.
The security policy documents the allowed HTTP path. It also records the absence of SSH access.
- Save README.md.
- Open the Markdown preview by pressing Ctrl+Shift+V.
- Confirm that the preview shows two tables with aligned headings.
- Confirm that the addressing table contains all three CIDR ranges.
Tables not rendering?
Confirm that every table row starts and ends with a vertical bar. Keep a blank line between each heading and its table.
Ask for help with the Markdown tables:
- Return to the editor view by pressing Ctrl+Shift+V.
- Append the test results and evidence links by pasting this content below the security policy:
## Test evidence
| Test | Expected result | Observed result |
| --- | --- | --- |
| HTTP before the inbound rule | Blocked | Browser could not reach the server |
| HTTP after adding TCP port 80 | Allowed | Apache page loaded |
| SSH exposure review | No port 22 rule | No SSH rule present |
| Private subnet route review | No direct internet route | No internet gateway route present |
## Evidence
### VPC resource map

### HTTP blocked by the security group

### HTTP allowed after the least-privilege rule

Why link the evidence this way?
The test table pairs each expected result with the result you observed. This turns the controlled failure into reproducible security evidence.
The image paths are relative to README.md. GitHub uses those paths to render images stored in the repository.
- Save README.md.
- Open the Markdown preview by pressing Ctrl+Shift+V.
- Confirm that the test table shows four security checks.
- Confirm that the preview loads all three evidence images.
Evidence images missing?
Confirm that the folder is named evidence. Check each image filename against the path in README.md.
Ask for help with the image paths:
- Return to the editor view by pressing Ctrl+Shift+V.
- Append the security decisions and limitations by pasting this content below the evidence section:
## Security decisions
- I created the security group before launching the instance and left inbound access empty to establish a secure default.
- I used user data instead of opening SSH for manual server configuration.
- I added only HTTP on TCP port 80 after observing the expected failure.
- I omitted a NAT gateway to avoid unnecessary cost in this learning environment.
- I created a private subnet to document workload separation, but intentionally left it empty in this first project.
## Limitations and production improvements
- The lab uses one Availability Zone and is not highly available.
- HTTP does not encrypt data in transit. A production version should use HTTPS.
- The instance has a public IPv4 address. A production design could place compute in private subnets behind a managed ingress layer.
- The infrastructure was created manually for learning. A follow-on project should reproduce it with infrastructure as code.
- Centralized flow logging and threat detection are deferred to a dedicated monitoring project.
Why document trade-offs?
The security decisions connect each configuration choice to a reason. The limitations show that you can separate a focused learning design from a production architecture.
- Save README.md.
- Open the Markdown preview by pressing Ctrl+Shift+V.
- Confirm that the preview shows five security decisions.
- Confirm that the preview shows five production limitations.
Bullets merging together?
Keep each bullet on a separate line. Leave one blank line between the final security decision and the next heading.
Ask for help with the lists:
- Return to the editor view by pressing Ctrl+Shift+V.
- Append the cost record and skills list by pasting this content below the limitations section:
## Cost controls and cleanup
- Used an AWS Free plan account and a Zero spend budget.
- Used a Free Tier eligible `t3.micro` instance in Standard credit mode.
- Created no NAT gateway.
- Terminated the Amazon EC2 instance after collecting evidence.
- Deleted the lab VPC after the instance terminated.
## Skills demonstrated
- Amazon VPC design
- CIDR planning and subnet segmentation
- Route-table and internet-gateway analysis
- Stateful security-group policy
- Amazon EC2 user data automation
- Controlled failure and connectivity troubleshooting
- Cloud cost management
- Technical documentation and evidence handling
What does this section add?
The cost record shows that resource lifecycle decisions are part of the lab. The skills list translates the technical work into capabilities a reviewer can recognize.
- Save README.md.
- Open the Markdown preview by pressing Ctrl+Shift+V.
- Confirm that the preview shows the cost controls section.
- Confirm that the preview shows eight demonstrated skills.
Section order looks wrong?
Place Cost controls and cleanup immediately after the limitations list. Keep Skills demonstrated below the cleanup section.
Ask for help with the section order:
- Return to the editor view by pressing Ctrl+Shift+V.
- Append the achievement statements by pasting this final content below the skills list:
## CV-ready achievement statements
- Designed and tested a segmented AWS VPC with public and private subnets, explicit routing, and least-privilege security-group controls.
- Automated deployment of an Apache web server on Amazon EC2 and diagnosed a deliberately blocked HTTP path before applying the minimal ingress rule.
- Published a reproducible cloud security case study with architecture, test evidence, threat decisions, limitations, and cleanup controls.
Why finish with achievements?
These statements lead with actions you completed. Each one connects a technical task to an observable security result.
- Save README.md.
- Open the Markdown preview by pressing Ctrl+Shift+V.
- Confirm that the final section contains three achievement statements.
Final section not visible?
Confirm that the achievement heading starts on a new line. Check that it appears below the final skill.
Ask for help with the final section:
Use the comparison below to confirm that every section appears in the required order.
✔️ Awesome, I've got everything!
Your README.md now documents the architecture, tests, trade-offs, cleanup record, skills, and achievement statements.
ⓧ I'd like to double check the full code
# Hardened AWS VPC Web Lab
A cloud networking and cybersecurity lab that demonstrates network segmentation, routing, least-privilege ingress, controlled failure, and evidence-based troubleshooting in AWS.
## Project outcome
I designed a custom Amazon VPC, launched an Apache web server in its public subnet, proved that an empty inbound rule set blocked the application, and restored only the required HTTP path without exposing SSH.
## Architecture
```mermaid
graph TD;
Internet-->InternetGateway;
InternetGateway-->PublicRouteTable;
PublicRouteTable-->PublicSubnet;
PublicSubnet-->EC2WebServer;
SecurityGroup-->EC2WebServer;
VPC-->PublicSubnet;
VPC-->PrivateSubnet;
```
## Addressing plan
| Component | CIDR | Purpose |
| --- | --- | --- |
| VPC | `10.0.0.0/16` | Overall lab address space |
| Public subnet | `10.0.1.0/24` | Internet-routed web tier |
| Private subnet | `10.0.2.0/24` | Isolated future application or data tier |
## Security policy
| Direction | Protocol | Port | Source or destination | Reason |
| --- | --- | --- | --- | --- |
| Inbound | TCP | 80 | `0.0.0.0/0` | Permit the public HTTP demonstration |
| Inbound | TCP | 22 | Not allowed | Avoid exposing SSH administration |
| Outbound | All | All | `0.0.0.0/0` | Allow initial package installation and responses |
## Test evidence
| Test | Expected result | Observed result |
| --- | --- | --- |
| HTTP before the inbound rule | Blocked | Browser could not reach the server |
| HTTP after adding TCP port 80 | Allowed | Apache page loaded |
| SSH exposure review | No port 22 rule | No SSH rule present |
| Private subnet route review | No direct internet route | No internet gateway route present |
## Evidence
### VPC resource map

### HTTP blocked by the security group

### HTTP allowed after the least-privilege rule

## Security decisions
- I created the security group before launching the instance and left inbound access empty to establish a secure default.
- I used user data instead of opening SSH for manual server configuration.
- I added only HTTP on TCP port 80 after observing the expected failure.
- I omitted a NAT gateway to avoid unnecessary cost in this learning environment.
- I created a private subnet to document workload separation, but intentionally left it empty in this first project.
## Limitations and production improvements
- The lab uses one Availability Zone and is not highly available.
- HTTP does not encrypt data in transit. A production version should use HTTPS.
- The instance has a public IPv4 address. A production design could place compute in private subnets behind a managed ingress layer.
- The infrastructure was created manually for learning. A follow-on project should reproduce it with infrastructure as code.
- Centralized flow logging and threat detection are deferred to a dedicated monitoring project.
## Cost controls and cleanup
- Used an AWS Free plan account and a Zero spend budget.
- Used a Free Tier eligible `t3.micro` instance in Standard credit mode.
- Created no NAT gateway.
- Terminated the Amazon EC2 instance after collecting evidence.
- Deleted the lab VPC after the instance terminated.
## Skills demonstrated
- Amazon VPC design
- CIDR planning and subnet segmentation
- Route-table and internet-gateway analysis
- Stateful security-group policy
- Amazon EC2 user data automation
- Controlled failure and connectivity troubleshooting
- Cloud cost management
- Technical documentation and evidence handling
## CV-ready achievement statements
- Designed and tested a segmented AWS VPC with public and private subnets, explicit routing, and least-privilege security-group controls.
- Automated deployment of an Apache web server on Amazon EC2 and diagnosed a deliberately blocked HTTP path before applying the minimal ingress rule.
- Published a reproducible cloud security case study with architecture, test evidence, threat decisions, limitations, and cleanup controls.
What should match?
Compare the headings and section order with your saved file. Keep the CIDR ranges, evidence paths, policy values, and achievement statements unchanged.
Remove sensitive details
Public evidence should prove the result without revealing account-specific information. Your script should also contain only the commands needed to reproduce the web server.
Publishing screenshots can feel risky. This review keeps credentials, identifiers, and temporary addresses out of the repository.
- Select user-data.sh from the file tree.
- Compare it with this reference version:
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo " Hello from $(hostname -f) in the public subnet " > /var/www/html/index.html
What should the script contain?
The script updates packages. It installs and enables the web server.
The final line creates the test page. No credential or account-specific value belongs in this file.
- Confirm that your local user-data.sh matches the reference.
- Remove any AWS account ID from the three screenshots.
- Remove any resource ID from the three screenshots.
- Remove any email address from the three screenshots.
- Remove any credential from the three screenshots.
- Remove any temporary public IPv4 address from the three screenshots.
- Save each redacted image under its original filename.
- Confirm that the evidence folder still contains vpc-resource-map.png, blocked-http.png, and allowed-http.png.
The screenshots should still show the network map, blocked request, and successful response. Personal or account-specific values should be unreadable.
Unsure whether a detail is sensitive?
Keep the architecture labels and test result visible. Redact values that identify your account, resources, contact details, credentials, or temporary public address.
Ask for help reviewing the evidence:
Publish and verify the repository
The repository becomes useful evidence when a visitor can open it without your signed-in session. The final check tests the same public view an admissions reviewer sees.
- Return to the aws-vpc-security-lab repository from earlier.
- Select Add file.
- Select Upload files.
- Drag the local README.md file into the upload area.
- Drag the local user-data.sh file into the upload area.
- Drag the local evidence folder into the upload area.
- Enter Publish security lab evidence as the commit message.
- Select Propose changes.
You should return to the repository page. The file list should show README.md, user-data.sh, and the evidence folder.
Files missing after the upload?
Return to the upload page if a filename was absent from the proposed changes. Confirm that all three image files appear inside the uploaded evidence folder.
Ask for help with the upload:
- Copy the repository address from the browser address bar into this project value: your public repository URL.
Before you check the public view, do you expect the diagram and all three images to load without your signed-in session?
- Open a private browser window.
- Enter your public repository URL in the address bar.
- Confirm that the Mermaid architecture diagram renders.
- Confirm that all three evidence images load.
- Confirm that no AWS account ID appears.
- Confirm that no resource ID appears.
- Confirm that no credential appears.
- Confirm that no temporary public IPv4 address appears.
Strong finish. Your public repository now explains the network design and proves both sides of the security test without exposing sensitive values.
Secret mission
Restrict the Website to Your Public IP
Replace internet-wide HTTP access with a rule limited to your current public IPv4 address. Then prove the site works from your Windows computer but fails from a second network.
Clean Up Your Resources
Clean Up Your Resources
Choose whether to keep the live lab, pause its compute, or remove its cloud resources. The lab consumes AWS credits while its Amazon EC2 instance, Amazon EBS storage, and public IPv4 address exist.
Cost warning
The AWS Free plan prevents out-of-pocket charges unless you upgrade to a Paid plan or activate paid-only services. Your running instance and its supporting resources still consume plan credits.
The in-use public IPv4 address costs $0.005 per hour. Pause the instance if you plan to return soon. Delete the AWS lab if you have finished collecting evidence.
Resources you used:
- One t3.micro Amazon EC2 instance named vpc-web-lab with an Amazon EBS root volume and a metered public IPv4 address.
- One Amazon VPC named masters-security-lab with public and private subnets.
- Two route tables with an internet gateway attached through the public route table.
- A default network ACL and a security group named web-lab-sg.
The Zero spend budget remains as a cost safeguard. Your public GitHub repository and local project folder remain as project evidence.
Keep everything running
No action is needed. Choose this option if you are still actively demonstrating or testing the live website.
- Keep vpc-web-lab running only while you need the live demonstration.
- Read any notification email sent by the Zero spend budget.
- Retain aws-vpc-security-lab and the local project folder as your permanent evidence.
Pause - I'll come back to this later
Shut down the running instance to stop its compute usage. The networking resources remain ready for your return.
- Return to the Amazon EC2 console from earlier.
- Select vpc-web-lab in the instance list.
- Choose Instance state.
- Choose Stop instance.
- Confirm the stop in the prompt.
- Wait until the console shows that the instance has stopped.
What happens when the instance stops?
The stopped instance no longer consumes compute credits. Its Amazon EBS root volume remains provisioned and metered.
A later start typically assigns a new public IPv4 address. Use the new address when you test the website again.
Delete - I don't want to use this again
This option removes the metered AWS lab so it cannot consume more credits. Your public GitHub repository remains as permanent evidence.
Deleting cloud resources can feel drastic. These actions target only the AWS resources created for this lab.
Terminate the Amazon EC2 instance:
- Return to the Amazon EC2 console from earlier.
- Select vpc-web-lab in the instance list.
- Choose Instance state.
- Choose Terminate (delete) instance.
- Choose Terminate (delete) to confirm.
- Wait for termination to finish.
Delete the VPC and its networking resources:
- Return to the Amazon VPC console from earlier.
- Choose Your VPCs.
- Select masters-security-lab.
- Choose Actions.
- Choose Delete VPC.
- Enter delete in the confirmation field.
- Choose Delete.
What does deleting the VPC remove?
Deleting masters-security-lab also deletes its public and private subnets, route tables, internet gateway, network ACLs, and security groups. This includes web-lab-sg.
The console removes these networking components together. You do not need to delete them individually.
- Return to Your VPCs.
- Search the list for masters-security-lab.
You should no longer see masters-security-lab in the VPC list. Your metered AWS lab is now removed.
Nice Work!
Nice Work!
Outstanding job! Your hardened AWS network now serves its web page only to the public IPv4 address you approved.
You've learned how to:
- Design a custom Amazon VPC with public and private subnets that expose each routing boundary through route tables.
- Deploy Apache HTTP Server on Amazon EC2 through user data without opening SSH administration.
- Turn blocked and allowed HTTP tests into redacted GitHub evidence of least-privilege ingress.
- Complete the optional Secret Mission by restricting TCP port 80 to your current public IPv4 address. Prove the rule from two independent networks.
Ready to quiz yourself?