Skip to main content

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​

  1. Open the AWS console and go to EC2 → Instances → Launch instances.
  2. Name: my-first-server.
  3. AMI: select Ubuntu Server 24.04 LTS (64-bit x86).
  4. Instance type: t3.micro — free tier eligible and plenty for this exercise.
  5. 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.
  6. 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:

TypePortSourceWhy
SSH22My IPOnly you can SSH in
HTTP80Anywhere (0.0.0.0/0)Public web traffic
Custom TCP3000My IPTest 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:

  1. EC2 → Instances → select instance → Instance state → Terminate.
  2. Confirm the attached volume is set to Delete on termination (it is by default).

Common Problems​

SymptomLikely Cause
SSH times outSecurity 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 3000Rule allows only "My IP" — check you're on the same network
App gone after rebootYou skipped pm2 save or the pm2 startup command it printed