Hands-On: Your First EC2 Server
What You Will Build
By the end of this walkthrough you'll have an Ubuntu server running an Express app on port 80, reachable from a browser, and configured to restart automatically if the process crashes or the instance reboots.
Step 1 — Launch the Instance
- Open the AWS console and go to EC2 → Instances → Launch instances.
- Name:
my-first-server. - AMI: select Ubuntu Server 24.04 LTS (64-bit x86).
- Instance type:
t3.micro— free tier eligible and plenty for this exercise. - Key pair: click Create new key pair, name it
my-first-server-key, type RSA, format .pem. The private key downloads immediately — this is your only copy, so keep it safe. - Leave storage at the default 8 GB gp3 volume.
Don't click Launch yet — configure the security group first.
Step 2 — Configure the Security Group
In the Network settings panel, choose Create security group and set three rules:
| Type | Port | Source | Why |
|---|---|---|---|
| SSH | 22 | My IP | Only you can SSH in |
| HTTP | 80 | Anywhere (0.0.0.0/0) | Public web traffic |
| Custom TCP | 3000 | My IP | Test the app before putting it on port 80 |
Never open port 22 to 0.0.0.0/0 — bots scan for open SSH constantly. Now click Launch instance and wait for the state to become Running.
Step 3 — SSH Into the Server
Grab the Public IPv4 address from the instance details page, then from your terminal:
chmod 400 ~/Downloads/my-first-server-key.pem
ssh -i ~/Downloads/my-first-server-key.pem ubuntu@YOUR_PUBLIC_IP
The default user for Ubuntu AMIs is ubuntu. Accept the host fingerprint prompt and you're in.
Step 4 — Install Node.js
Use the NodeSource repository to get a current LTS release instead of the outdated version in Ubuntu's default repos:
sudo apt update && sudo apt upgrade -y
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
node -v # v22.x
npm -v
Step 5 — Create the Express App
mkdir ~/app && cd ~/app
npm init -y
npm install express
Create ~/app/server.js:
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.json({ message: 'Hello from EC2', uptime: process.uptime() });
});
app.get('/health', (req, res) => {
res.status(200).send('OK');
});
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});
Test it:
node server.js
Open http://YOUR_PUBLIC_IP:3000 in a browser — you should see the JSON response. Stop the server with Ctrl+C.
Step 6 — Keep It Alive with PM2
If you close the SSH session now, the app dies with it. PM2 runs the app as a managed background process:
sudo npm install -g pm2
cd ~/app
pm2 start server.js --name my-app
pm2 status
Make PM2 itself survive a reboot:
pm2 startup systemd
# PM2 prints a sudo command — copy and run it, then:
pm2 save
Useful PM2 commands you'll use daily:
pm2 logs my-app # tail logs
pm2 restart my-app # restart after deploying new code
pm2 monit # live CPU/memory dashboard
Step 7 — Serve on Port 80
Ports below 1024 require root, and running Node as root is a bad idea. Redirect port 80 to 3000 at the firewall level instead:
sudo apt install -y iptables-persistent
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 3000
sudo netfilter-persistent save
Now http://YOUR_PUBLIC_IP works without the port suffix. In a real production setup I put Nginx in front instead — it adds TLS termination, gzip, and static file serving — but the iptables redirect is enough to understand the flow.
Step 8 — Verify Everything Survives a Reboot
sudo reboot
Wait a minute, refresh the browser, and the app should come back on its own. If it does, PM2's startup hook and the persisted iptables rule are both working.
Cleanup
A stopped t3.micro costs nothing for compute, but the EBS volume still bills. When you're done experimenting:
- EC2 → Instances → select instance → Instance state → Terminate.
- Confirm the attached volume is set to Delete on termination (it is by default).
Common Problems
| Symptom | Likely Cause |
|---|---|
| SSH times out | Security group doesn't allow port 22 from your current IP (it changes on VPN/network switch) |
Permission denied (publickey) | Wrong key file, wrong user (use ubuntu), or chmod 400 not applied |
| Browser can't reach port 3000 | Rule allows only "My IP" — check you're on the same network |
| App gone after reboot | You skipped pm2 save or the pm2 startup command it printed |