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