WEEK 1 · LAB 2 (BUILD-ON-YOUR-OWN)

Launch EC2 + Deploy
Your First Web App

SSH into a real Linux server in another country — and serve a website from it

8
Steps
~75 min
Estimated time
CLF-C02
Cert aligned
Free Tier
$0 if you stop the instance

What you'll build

🔑

SSH Key Pair

The cryptographic key that lets you log into your server securely

🛡️

Security Group

A virtual firewall that only allows the traffic you specify

🖥️

EC2 Instance

A real Linux server, running on Amazon's hardware, controlled by you

🌐

Nginx Web Server

The same web server software powering Netflix, Airbnb, GitHub

🚀

Live Web Page

A real public URL serving HTML you wrote

Bonus: Node API

A tiny dynamic API that proves you can do more than static

Architecture

💻 You — laptop in Accra
↓ SSH (port 22) + HTTP (port 80)
🛡️ Security GroupAllows: SSH from your IP · HTTP from anywhere
🖥️ EC2 Instance (t2.micro)Amazon Linux 2023 · Public IP · 1 vCPU · 1 GB RAM
↓ runs
🌐 Nginx Web ServerServes your HTML on port 80
0

Before you start — what you need

You should have completed Lab 1 first

5 min

Read this first

Lab 1 was about static hosting — drop files in a bucket, done. Lab 2 is the next level: you control the actual server. You'll SSH in like a real engineer, install software, and run it. This is what people mean when they say "I deployed it to the cloud".

A few moments will feel weird at first — like getting a "permission denied" on a file YOU just downloaded. That's normal. We'll explain everything as we go.

Requirements
  • 1
    Lab 1 completed You should have an active AWS account with billing alarm set. If not, do Lab 1 first — it teaches the basics we'll build on here.
  • 2
    A working terminal You'll be typing SSH commands.
    Mac / Linux
    Built-in Terminal app. You're set.
    Windows
    Use WSL, Git Bash, or PowerShell. WSL recommended.
  • 3
    Find your current IP address Visit whatismyip.com and note it. We'll use it to lock SSH access to ONLY your laptop. AWS has a "My IP" button that auto-fills it.
⚠️ Free tier guard rail

EC2 t2.micro is FREE for 750 hours/month for 12 months. That's 31 days × 24 hours = 744 hours — enough to run ONE instance 24/7 all month. Don't run two at the same time — that doubles your hours and you'll get charged. Stop the instance when not using it.

✅ Ready to go when:

1

Create an SSH key pair

The "password" for your future server

5 min

What is an SSH key pair?

SSH (Secure Shell) is how you remotely log into a Linux server. Instead of using a password, AWS uses key pairs — two cryptographic files that work together: a public key (lives on the server) and a private key (stays on your laptop, NEVER shared). When you connect, the keys "shake hands" mathematically. Much more secure than a password.

Create the key pair
  • 1
    Navigate to EC2 → Key Pairs
  • 2
    Click "Create key pair" (orange button)
Fill in these values
Name
cloudwithshad-key
Key pair type
RSA
Private key format
.pem (Mac/Linux/WSL) — or .ppk if you must use PuTTY on Windows
  • 3
    Click "Create key pair" Your browser will IMMEDIATELY download a file called cloudwithshad-key.pem
⚠️ CRITICAL — save your .pem file safely RIGHT NOW

AWS will NEVER let you download this file again. If you lose it, you permanently lose access to any server using it. You'd have to delete the server and start over.

Recommended: create a folder called ~/aws-keys/ and move the .pem file there. Don't put it in Downloads where you'll accidentally clear it.

✅ Check before moving on:

2

Create a security group

The firewall that decides who can reach your server

8 min

What is a security group?

A security group is a virtual firewall attached to your EC2 instance. By default, it blocks ALL inbound traffic. You explicitly allow what you need:

  • Port 22 (SSH) — so YOU can log in from your laptop
  • Port 80 (HTTP) — so visitors can see your website

It's STATEFUL — return traffic is automatically allowed. You only configure the inbound side.

Create the security group
  • 1
    Go to Security Groups
Security group name
cloudwithshad-web-sg
Description
SSH from my IP + HTTP from anywhere
VPC
default (or your existing VPC)
Inbound rule 1 — SSH for YOU only
  • 2
    Click "Add rule" under Inbound rules
Type
SSH
Protocol/Port
TCP 22 (auto-fills)
Source
My IP (click dropdown — AWS auto-detects your IP)
Inbound rule 2 — HTTP for the world
  • 3
    Click "Add rule" again
Type
HTTP
Protocol/Port
TCP 80 (auto-fills)
Source
Anywhere-IPv4 (0.0.0.0/0)
💡 Why "My IP" for SSH but "Anywhere" for HTTP?

SSH gives shell access — full control of the server. You don't want bots in Russia trying to brute-force in. Restricting to your IP means even if someone steals your .pem, they can't use it from a different network.

HTTP is for your visitors — they could be anywhere in the world, so we open it to everyone. The web server itself only serves the files you give it; no shell access.

  • 4
    Leave Outbound rules as default Default = "all traffic to anywhere". The server needs to reach the internet to download Nginx etc.
  • 5
    Click "Create security group"

✅ Check before moving on:

3

Launch your EC2 instance

Click a few buttons → real Linux server

10 min
  • 1
    Go to EC2 → Instances
Name and Image
Name
cloudwithshad-web-server
AMI
Amazon Linux 2023 AMI (Free tier eligible — green badge)
Architecture
64-bit (x86)
Instance type
Type
t2.micro (Free tier eligible)
ℹ️ What's in a t2.micro?

1 vCPU, 1 GB RAM. Tiny — but plenty for learning, testing, and small workloads. Real production apps use bigger instances (t3.large, m5.xlarge, etc.) but the launch process is identical.

Key pair
Key pair
cloudwithshad-key (select from dropdown)
Network settings — click "Edit"
VPC
Default (or matches your SG's VPC)
Subnet
No preference
Auto-assign public IP
Enable ← critical, else you can't SSH in
Firewall (SG)
Select existing → cloudwithshad-web-sg
Storage
Size
8 GiB gp3 (default — free tier covers up to 30 GB)
  • 2
    Click "Launch instance" AWS provisions the server in 1–2 minutes. Status will go from "Pending" → "Running" → and after about a minute, "Status check: 2/2 checks passed".
  • 3
    Note the Public IPv4 address Once running, click on your instance → look for Public IPv4 address in the details panel. Looks like 54.123.45.67. Write it down.
📸 Screenshot moment — capture your running instance with its public IP. You're now controlling a server in another country.

✅ Check before moving on:

4

SSH into your server

The "I'm actually inside the cloud" moment

10 min

What's about to happen

You're going to open a terminal on your laptop in Accra → connect over SSH to a Linux server in (probably) Virginia → and your typing will run on that remote machine, not yours. The first time it works, it feels magical.

Fix .pem file permissions (one-time setup)
  • 1
    Open your terminal and navigate to where the .pem is e.g. cd ~/aws-keys
Terminal — fix key permissions (Mac/Linux/WSL)
# SSH refuses to use a key that's readable by other users — security feature
chmod 400 cloudwithshad-key.pem

# Verify — should show: -r-------- 1 you you ... cloudwithshad-key.pem
ls -la cloudwithshad-key.pem
SSH into the server
Terminal — replace with YOUR public IP
# -i = identity file (your private key)
ssh -i cloudwithshad-key.pem ec2-user@YOUR_PUBLIC_IP

# Real example:
ssh -i cloudwithshad-key.pem ec2-user@54.123.45.67
💡 First time SSHing — "Are you sure you want to continue?"

You'll see something like: "The authenticity of host '54.x.x.x' can't be established. Are you sure you want to continue connecting (yes/no)?". Type yes + Enter. This is normal — SSH is asking you to verify it's the right server. After you say yes, it remembers and won't ask again.

✅ What you should see after connecting

  • An ASCII Amazon Linux banner
  • Your prompt changes to something like: [ec2-user@ip-172-31-x-x ~]$
  • You're now inside the server — every command you type runs there
Prove you're really there
From inside the server
# What machine am I on?
hostname

# What OS?
cat /etc/os-release

# Where in the world is this server?
curl http://169.254.169.254/latest/meta-data/placement/region

# How much RAM and CPU does it have?
free -h
nproc
📸 Screenshot — show your terminal connected to the EC2 instance with the prompt visible. This is the screenshot that will impress your non-technical friends.

🔧 Common SSH problems

"Permission denied (publickey)"
You forgot chmod 400 OR you're using the wrong username. For Amazon Linux always use ec2-user. (For Ubuntu it would be ubuntu.)
"Connection timed out"
Either the security group doesn't allow your IP, OR your IP changed since you set up the SG. Go to EC2 → Security Groups → cloudwithshad-web-sg → Inbound rules → edit the SSH rule → "My IP" again.
"Unprotected private key file"
You skipped the chmod step. Run chmod 400 cloudwithshad-key.pem first.
"Could not resolve hostname"
Check there are no typos in your public IP. Make sure you used the IPv4, not IPv6.

✅ Check before moving on:

5

Install Nginx

The world's most popular web server

10 min

Why Nginx?

Nginx (pronounced "engine-X") is a web server that takes incoming HTTP requests and returns HTML pages. It powers Netflix, Airbnb, Cloudflare, GitHub, and most of the high-traffic web. It's free, fast, and easier to learn than Apache.

Inside the EC2 SSH session — install & start
Run these on the EC2 server (one at a time)
# Update the package manager (good habit on a fresh server)
sudo dnf update -y

# Install nginx
sudo dnf install -y nginx

# Start the service
sudo systemctl start nginx

# Make it auto-start on reboot
sudo systemctl enable nginx

# Verify it's running — should say "active (running)"
sudo systemctl status nginx
💡 What's sudo?

sudo = "Super User DO". Linux protects system-level operations (installing software, starting services) by requiring elevated privileges. Putting sudo in front of a command says: "run this as the admin (root) user". It's a safety feature.

Test it from your laptop
  • 1
    Open a browser on your laptop
  • 2
    Visit your server's public IP Just paste the public IP into the URL bar — no https:// needed (we're using port 80, plain HTTP).

✅ What you should see

  • Default Nginx welcome page: "Welcome to nginx!"
  • This means: your laptop hit your security group → which let port 80 through → which reached your EC2 → which Nginx answered with its default page
⚠️ Browser shows error or hangs?

Most likely: you typed https:// — remove the s. We didn't set up HTTPS yet, only HTTP. OR check the security group has port 80 open from 0.0.0.0/0.

✅ Check before moving on:

6

Deploy your own HTML page

Replace the default Nginx page with something yours

12 min

How web servers serve files

Nginx looks in a specific folder on the server (called the "document root") whenever a request comes in. On Amazon Linux, that folder is /usr/share/nginx/html/. We replace the default index.html there with our own.

Edit the index.html on the server
Inside SSH — back up old + create new
# Back up the default page (just in case)
sudo mv /usr/share/nginx/html/index.html /usr/share/nginx/html/index.html.bak

# Create your own (using a heredoc — multi-line input)
sudo tee /usr/share/nginx/html/index.html > /dev/null <<'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>My EC2 Server | cloudwithshad</title>
  <style>
    body { font-family: system-ui, sans-serif; background: #0A1628;
           color: white; max-width: 720px; margin: 60px auto;
           padding: 0 20px; line-height: 1.6; }
    h1 { color: #00B4D8; font-size: 42px; }
    .badge { background: #00B4D8; color: #0A1628; padding: 4px 10px;
             border-radius: 14px; font-size: 13px; font-weight: 700; }
    .info { background: rgba(255,255,255,0.05); padding: 16px;
            border-radius: 8px; margin-top: 20px; }
    code { background: rgba(255,255,255,0.1); padding: 2px 6px;
           border-radius: 3px; }
  </style>
</head>
<body>
  <span class="badge">LIVE FROM EC2</span>
  <h1>Hello from my AWS server!</h1>
  <p>This page is being served by Nginx running on Amazon Linux 2023,
     on a t2.micro EC2 instance.</p>
  <div class="info">
    <p>Built in Week 1 of the cloudwithshad bootcamp by <strong>Your Name</strong>.</p>
    <p>Stack: <code>EC2</code> · <code>Nginx</code> · <code>Linux</code></p>
  </div>
</body>
</html>
EOF

# Reload Nginx to pick up the change (faster than restart)
sudo systemctl reload nginx
💡 Why tee and not nano?

You CAN use sudo nano /usr/share/nginx/html/index.html if you prefer an editor — it works fine. The tee + heredoc approach lets us paste a whole file in one command, which is reproducible and copy-friendly. Both are valid.

Refresh your browser
  • 1
    Refresh http://<your-IP> in your browser You should see your dark-themed page with "Hello from my AWS server!"
  • 2
    Edit your name in the HTML and reload Nginx SSH session: sudo nano /usr/share/nginx/html/index.html → change "Your Name" → save (Ctrl+O, Enter, Ctrl+X) → sudo systemctl reload nginx → refresh browser
📸 Screenshot — your custom page hosted on YOUR server. Different from Lab 1 because YOU control the underlying machine.
📝 Cloud Practitioner Exam Tip

S3 vs EC2 for static sites — both can do it. S3 is cheaper, simpler, scales automatically. EC2 is more flexible (run any code) but you manage the server yourself. The exam asks "which would you recommend for a static site with no backend?" — answer: S3.

✅ Check before moving on:

7

BONUS — Run a tiny Node.js API

Prove your server can do more than static files

15 min

This is optional but worth doing

So far you served static HTML — same as S3 could do. Let's prove the EC2 advantage by running an actual program: a tiny Node.js HTTP server that returns a different message every time you reload.

Install Node.js
Inside SSH
# Install Node.js (Amazon Linux 2023 has it as a package)
sudo dnf install -y nodejs

# Verify version
node --version
Create a tiny API server
Inside SSH — create app.js
cat > ~/app.js <<'EOF'
const http = require('http');
const facts = [
  "S3 stands for Simple Storage Service.",
  "EC2 stands for Elastic Compute Cloud.",
  "AWS has 30+ regions globally.",
  "CloudFront has 400+ edge locations.",
  "t2.micro has 1 vCPU and 1 GB RAM.",
  "IAM means Identity and Access Management."
];

http.createServer((req, res) => {
  const fact = facts[Math.floor(Math.random() * facts.length)];
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({
    message: "Hello from Node.js on EC2!",
    random_aws_fact: fact,
    timestamp: new Date().toISOString()
  }));
}).listen(3000);

console.log('API listening on port 3000');
EOF

# Run it (will block your terminal — that's fine)
node ~/app.js
Open a SECOND inbound rule for port 3000
  • 1
    In another browser tab, go to your security group
  • 2
    Add a new rule Type: Custom TCP · Port: 3000 · Source: Anywhere-IPv4 (0.0.0.0/0) · Save
Test from your laptop
  • 3
    Visit http://<your-IP>:3000 in your browser You should see JSON with a random AWS fact. Refresh — different fact. THAT'S a dynamic API.

✅ What you should see

  • JSON response: {"message":"Hello from Node.js on EC2!","random_aws_fact":"...","timestamp":"..."}
  • Each refresh shows a different fact
  • Your terminal in SSH is showing the running Node process
🚀 What you just did

You ran custom code on a server you control, listening on a port you exposed, returning data you generated. This is what an API is. Backend developers build apps exactly like this — just bigger. Frameworks like Express, Flask, FastAPI all do the same thing under the hood.

💡 To stop the Node server

Press Ctrl+C in the SSH session where it's running. To run it in the background even after you log out, use nohup node ~/app.js & — but for learning, just keep the terminal open.

✅ Check:

🎉 Lab 2 Complete!

You launched and managed a real Linux server in the cloud. You SSH'd in, installed software, and served custom content. This is the foundational skill of every cloud engineer.

Final checklist:
🗑️ Cleanup — DO THIS unless you really want to keep the server running

Unlike S3 (which is basically free at small sizes), a running EC2 instance accumulates hours every minute. Free tier covers 750 hrs/month — but if you launch a second instance for any lab, you double-count. Safest: stop or terminate when done.

1
Option A: STOP (keeps the data, pause billing for compute)
EC2 → Instances → select instance → Instance state → Stop instance
You can restart it later. Note: stopping gives a NEW public IP on restart.
2
Option B: TERMINATE (delete the server entirely)
EC2 → Instances → select instance → Instance state → Terminate instance → confirm
Permanent. The instance is destroyed and you can't get it back. Most thorough cleanup.
3
Delete the security group (only after instance is terminated)
EC2 → Security Groups → cloudwithshad-web-sg → Delete
4
Keep the key pair
You'll reuse cloudwithshad-key in future labs. No charge for storing key pairs in AWS.
⚠️ Verify cleanup

After cleanup, go to EC2 → Instances and confirm: either (a) instance shows "Stopped" or "Terminated", and (b) no other running instances exist. If anything is "running", it's accumulating hours.

What's next — Lab 3 + Week 2

🎮

Lab 3 — Build the Architecture

Interactive quiz where you assemble Week 1's architecture by answering questions correctly

🧠

Week 2 — IAM Deep Dive

Users, roles, policies — the security backbone of every AWS account

🌐

Week 2 — Networking

VPCs, subnets, route tables — wire up a real production-style network