AWS 📖 16 min read

Launch Your First EC2 Instance

You end up with one small EC2 instance in your default VPC, a key pair that works from your own terminal, a security group that only lets your IP reach SSH, and Nginx answering on port 80. It's for someone launching their first instance who wants to know what each setting in the launch wizard does before automating anything.

What You Need

  • Hardware: Any machine with a browser and a terminal
  • Software: An AWS account with billing set up, an SSH client (OpenSSH on Linux, macOS and Windows 10/11, or PuTTY). The steps target Amazon Linux 2023 and Ubuntu Server 24.04 LTS; Ubuntu 26.04 LTS, which also appears under Quick Start, uses the same commands.
  • Time: 20-40 minutes

Have these ready before you start:

  • AWS account you can sign in to (preferably as an administrator user, not root; see the IAM guide)
  • Region selected in the top navigation bar of the console (pick one near you)
  • Terminal app ready (Terminal on Mac/Linux, PowerShell or WSL on Windows)

The key pair and security group are created during the launch steps below.

The console labels below follow AWS's own Get started with Amazon EC2 tutorial; if the console changes, that page is the reference.

Step 1: Launch the Instance

Terminal window with the commands from the section Step 1: Launch the Instance
Ask AWS which instance types are free-tier eligible in your region.
💸 Cost warning: An instance bills from launch until you stop or terminate it, even when idle. Whether it's free depends on your account (see How Much Will This Cost? below). The real risk is forgetting to terminate afterward, so set a reminder or terminate the instance as soon as you finish the last step. Current rates are on the EC2 On-Demand pricing page.
  1. Open the EC2 console (or type EC2 in the console search bar)
  2. Check the Region shown in the navigation bar, then on the dashboard choose Launch instance

Name and Tags

Type something descriptive: nginx-test works. Tags are metadata attached to the resource — useful later, optional now.

Choose an AMI (Operating System)

AMI = Amazon Machine Image. It's the OS that boots on your instance. Under Application and OS Images (Amazon Machine Image), choose Quick Start and you'll see a short list. The two most common choices: Amazon Linux 2023 (uses dnf, comes with the AWS CLI preinstalled; AWS's own tutorial recommends it for a first Linux instance) and Ubuntu Server 24.04 LTS or 26.04 LTS (uses apt, and most general Linux tutorials, including the other guides on this site, assume a Debian/Ubuntu system). Pick an AMI marked Free tier eligible if you want to stay inside the free tier.

The rest of this tutorial covers both, so pick what you know. If you plan to follow other Linux guides afterwards, Ubuntu keeps the commands consistent.

Instance Type

Instance type controls CPU and RAM. For a tutorial Nginx box, t3.micro (2 vCPUs, 1 GiB) is a good default and is Free tier eligible on the current credit-based plan. t2.micro (1 vCPU, 1 GiB) isn't. It was a legacy Free Tier type, and older accounts' 12-month legacy period has ended (see How Much Will This Cost?). The label marks instance types, not whether your account has credits left. With the AWS CLI configured, this lists the eligible types for the current Region:

Terminal
aws ec2 describe-instance-types \
  --filters Name=free-tier-eligible,Values=true \
  --query "InstanceTypes[*].InstanceType" --output text

Both are burstable "T" types: they run at a baseline CPU level and spend CPU credits to go above it. That's fine for a web server that is mostly idle, but a sustained CPU-heavy job will run out of credits. See burstable performance instances for how credits work.

Key Pair (SSH Access)

EC2 uses asymmetric key authentication. AWS holds the public half; you download the private half as a .pem file.

  1. Under Key pair (login), choose Create new key pair
  2. Name: my-aws-key
  3. Type: ED25519 or RSA, Format: .pem (select .ppk instead if you plan to use PuTTY)
  4. Click Create — the file downloads once, right now, and cannot be re-downloaded

Move it somewhere permanent immediately:

Move the key file (Linux/Mac)
mv ~/Downloads/my-aws-key.pem ~/.ssh/
chmod 400 ~/.ssh/my-aws-key.pem

The chmod 400 is not optional. OpenSSH refuses to use a private key that other users can read: it prints an "UNPROTECTED PRIVATE KEY FILE" warning saying the permissions are too open, ignores the key, and the login then fails with "Permission denied (publickey)". The warning scrolls past easily, so make it a reflex: download the key, move it, chmod 400, done. (Understanding Linux Permissions explains what 400 means.)

On Windows, the OpenSSH client in PowerShell runs the same check against NTFS permissions. A key stored in your own user profile normally passes, because only your account, SYSTEM and Administrators can read it. If you still get the warning, open the file's Properties → Security → Advanced, disable inheritance, and remove every other user and group. To move it into place from PowerShell:

PowerShell
New-Item -ItemType Directory -Force "$env:USERPROFILE\.ssh" | Out-Null
Move-Item "$env:USERPROFILE\Downloads\my-aws-key.pem" "$env:USERPROFILE\.ssh\"

Keep a copy somewhere safe, such as a password manager. AWS keeps only the public key, so a lost .pem can't be downloaded again.

Network Settings (Security Group)

This is the part that matters most and the part people rush through. A security group is a stateful firewall that sits in front of your instance. Every inbound packet gets checked against these rules before it touches the OS. No matching rule, no connection — the packet just disappears silently.

Under Network settings, the console proposes the default VPC, a default subnet and a new security group. Keep the new security group, but don't accept its default SSH source: AWS's own tutorial points out that it allows connections from anywhere (0.0.0.0/0). Change the SSH source and add web traffic so you end up with three inbound rules:

  1. SSH (port 22) — set source to My IP. This locks SSH access to your current public address.
  2. HTTP (port 80) — source: Anywhere (0.0.0.0/0). Nginx needs this.
  3. HTTPS (port 443) — source: Anywhere. Not strictly needed yet, but add it now so you don't have to come back when you set up TLS later.

The critical line is the SSH rule. Automated scanners continuously probe public IPv4 addresses for open SSH ports and try common usernames and passwords, so a port 22 open to 0.0.0.0/0 starts collecting login attempts soon after the instance gets its address. Key-only authentication stops those attempts from succeeding, but restricting the source to My IP keeps them from reaching sshd at all.

One gotcha: "My IP" means your public IP right now. If you're on a coffee shop Wi-Fi and go home, your IP changes and SSH will time out. You'll need to go back to the console and update the security group rule with your new IP. The console's browser-based EC2 Instance Connect button won't help here either: its traffic comes from AWS-managed address ranges, so it only works if the security group also allows port 22 from the com.amazonaws.region.ec2-instance-connect prefix list (see the EC2 Instance Connect prerequisites). Editing the rule's source is the quicker fix.

The result of this configuration: only your specific IP address can reach port 22. The entire internet can reach ports 80 and 443. Outbound traffic is unrestricted by default (your instance can reach the internet for package installs, API calls, etc.).

Storage

Under Configure storage, leave the default root volume the AMI proposes. Nginx and a static page need very little disk. If you want to stay inside the free tier, keep the total EBS size within your account's free tier storage allowance.

Launch!

Review the Summary panel and choose Launch instance. Open the instance from the success message, then wait until Instance state reads "Running" and the status checks have passed (the Status and alarms tab shows them). Only then will SSH connections succeed. From the CLI, both columns should read ok:

Terminal
aws ec2 describe-instance-status --instance-ids i-0abc123def456789 \
  --query "InstanceStatuses[].[InstanceStatus.Status, SystemStatus.Status]" --output text

Step 2: Connect via SSH

Grab the Public IPv4 address from the instance details panel in the console — something like 54.123.45.67.

Linux/Mac

Terminal
ssh -i ~/.ssh/my-aws-key.pem ec2-user@54.123.45.67

Swap ec2-user for ubuntu if you picked the Ubuntu AMI (Debian uses admin). SSH will ask you to confirm the host fingerprint the first time — type yes and you'll land at a shell prompt. Confirm you're on the right machine with whoami and grep PRETTY_NAME /etc/os-release.

Windows

Windows 10/11 ships with OpenSSH in PowerShell. The same ssh -i command works there. Alternatively, install WSL and use the Linux instructions above.

If you prefer a GUI, PuTTY works too:

  1. Convert the .pem to .ppk with PuTTYgen
  2. Open PuTTY, paste the public IP into the Host Name field
  3. Navigate to Connection → SSH → Auth → Credentials
  4. Browse to the .ppk file
  5. Click Open

If SSH Hangs or Times Out

A hang followed by a timeout means packets are being dropped before they reach sshd, and the most common cause is the security group. Go back to the console, open the security group attached to the instance, and check that the port 22 inbound rule lists your current public IP — not the IP you had when you created the rule yesterday on a different network. If the IP is right, confirm the instance is in a public subnet with an internet gateway and that it actually has a public IPv4 address assigned (check the details panel — sometimes auto-assign public IP gets toggled off).

Step 3: Install a Web Server

With a shell open on the instance, install Nginx so there is something to see on port 80.

Terminal window with the commands from the section Step 3: Install a Web Server
Install, start and enable Nginx on Amazon Linux with dnf.

Amazon Linux 2023

Bash
# Update packages
sudo dnf update -y

# Install Nginx
sudo dnf install nginx -y

# Start and enable
sudo systemctl start nginx
sudo systemctl enable nginx

💡 Note: If dnf install hangs or times out, confirm the instance has outbound internet access. Default VPC setups include this, but custom VPCs may not.

Ubuntu

Bash
# Update packages
sudo apt update && sudo apt upgrade -y

# Install Nginx
sudo apt install nginx -y

# It starts automatically on Ubuntu

On either distribution, check it from the instance before trying a browser. The first command should print active and the second an HTTP/1.1 200 OK line:

Bash
systemctl is-active nginx
curl -I http://localhost

Test It

Open a browser and go to http://54.123.45.67 (your instance's public IP). Type the http:// explicitly: some browsers try HTTPS first, and nothing listens on port 443 yet. The Nginx welcome page means the instance, the security group's port 80 rule and the web server all work. If curl on the instance worked but the browser times out, the port 80 rule is the problem.

Step 4: Basic Hardening

What you have so far works for a quick test. If the instance stays up more than a few hours, tighten things up.

Terminal window with the commands from the section Step 4: Basic Hardening
Fail2Ban on Ubuntu: install it, then confirm the service is running.

Update the System Regularly

Run sudo dnf update -y (Amazon Linux) or sudo apt update && sudo apt upgrade -y (Ubuntu) regularly. Software with publicly known vulnerabilities on a public IP is an easy target, and patching is the cheapest defence you have. On Ubuntu, the unattended-upgrades package can apply security updates automatically.

Check Your Security Group

Go to your instance in the console, click the Security tab, click the security group link. The SSH rule source should be your IP, not 0.0.0.0/0. If it says 0.0.0.0/0, fix it now — that means every IP on the internet can attempt SSH connections.

Install Fail2Ban

Bash
# Ubuntu
sudo apt install fail2ban -y
sudo systemctl status fail2ban

Fail2Ban watches authentication logs and temporarily bans IPs after repeated failed login attempts. With SSH already limited to your IP it matters less, but it helps if you ever have to open the port more widely. Package availability differs on Amazon Linux 2023, so check your configured repositories before relying on it there. Jail configuration, including the journald backend that newer Debian-based systems need, is covered in Locking Down SSH.

Use SSH Keys Only (Already Done)

The Amazon Linux and Ubuntu AMIs are set up for key-based login, with SSH password authentication turned off. Check the effective setting rather than trusting the default:

Bash
sudo sshd -T | grep -i passwordauthentication

It should print passwordauthentication no. Leave it that way.

How Much Will This Cost?

It depends on when your AWS account was created. Per the EC2 Free Tier page in October 2026, the credit-based plan is the one in effect:

  • Created on or after July 15, 2025: new accounts get a USD 100 sign-up credit and can earn up to USD 100 more. On the free account plan, the free period ends after 6 months or when the credits are used up, whichever comes first, and you can't be charged during it. Free tier eligible types are t3.micro, t3.small, t4g.micro, t4g.small, c7i-flex.large and m7i-flex.large. The t4g types are Arm (Graviton), so pick an arm64 AMI for them.
  • Created before July 15, 2025: the legacy 12-month free tier has ended for all of these accounts (the last ran out in July 2026), so EC2 usage is billed at normal rates.

Outside the free tier you pay the hourly on-demand rate for the instance, plus EBS storage, plus USD 0.005 per hour for each public IPv4 address (about USD 3.60 for a 30-day month), per the VPC pricing page. Stopping the instance ends compute charges, but you still pay for the EBS volume. Rates differ per Region; check the EC2 pricing page and the Billing console rather than relying on numbers in a tutorial.

Stopping vs Terminating

Stop shuts the VM down but keeps the EBS volume: compute billing ends, storage billing continues, and starting it again assigns a new public IP unless you attach an Elastic IP. Terminate deletes the instance and, by default, its root volume; it can't be undone. Volumes you attached with "Delete on termination" turned off survive and keep billing. For this tutorial, terminate when you're done, since there's nothing on the instance worth keeping.

Common Issues

"Permission denied (publickey)"

Three possible causes: you specified the wrong .pem file, you used the wrong username for the AMI, or the key file permissions are not restrictive enough. Run chmod 400 on the key and double-check the username (ec2-user for Amazon Linux, ubuntu for Ubuntu).

"Connection timed out"

Either the security group does not have an inbound rule for port 22 from your IP, or the instance sits in a private subnet without a route to the internet gateway.

Can't access the website

Verify the security group has an inbound rule for HTTP on port 80 with source 0.0.0.0/0. Then confirm Nginx is actually running: sudo systemctl status nginx. If the service is inactive, start it with sudo systemctl start nginx.

"REMOTE HOST IDENTIFICATION HAS CHANGED"

You connected to this IP address before, and a different machine now answers on it. That's expected after you terminate an instance and a new one gets the same address, or after you move an Elastic IP. If you know that's what happened, remove the old entry with ssh-keygen -R 54.123.45.67 and connect again. If you didn't change anything, stop and find out why the host key changed before you type a password or accept the new key.

Forgot key pair

AWS cannot regenerate a lost private key. You can recover access the hard way: stop the instance, detach its root volume, attach it to a second instance, edit ~/.ssh/authorized_keys on the mounted volume, reattach, and restart. For a tutorial instance, just terminate it and launch a new one with a fresh key pair.

How the Pieces Fit Together

The full picture, end to end:

Diagram: internet traffic passes a security group allowing ports 22, 80 and 443 to an EC2 instance running Amazon Linux or Ubuntu with Nginx and key-based SSH.
The whole setup: the security group is the firewall in front of the instance, and only ports 22, 80 and 443 get through.
Architecture
Internet
 ↓
Security Group (firewall - port 22, 80, 443)
 ↓
EC2 Instance (virtual machine)
 └── Amazon Linux / Ubuntu
 └── Nginx web server
 └── Your key-based SSH access

Every inbound packet passes through the security group first. Only traffic matching an explicit allow rule reaches the instance. We opened three ports: 22 for SSH, 80 for HTTP, and 443 for HTTPS.

Where to Go Next

Once the Nginx welcome page loads, you have a working server. Some things worth doing next, all on the same instance:

  • Serve your own HTML — drop files into /var/www/html/ (Ubuntu) or /usr/share/nginx/html/ (Amazon Linux); Nginx Web Server Setup covers server blocks properly
  • Add HTTPS — point a domain at the instance and get a Let's Encrypt certificate with Certbot, as in the HTTPS section of the EC2 Deployment Guide
  • Run an application server — Node.js, Python, Go, whatever your project uses; the AWS EC2 Deployment Guide picks up from here
  • Attach a database — PostgreSQL or MariaDB installed locally, or connect to RDS

EC2 vs Serverless

EC2 gives you a full VM — you own the OS, the runtime, and the network config. That means you also own the patching, the monitoring, and the capacity planning. Use EC2 for long-running daemons, WebSocket servers, databases, or anything that needs specific kernel modules. Use Lambda when the workload is short-lived request handlers or event-driven glue code and you don't want to think about servers at all (see Your First Serverless Lambda Function).

Cleanup

When you are done:

Terminal
# Terminate the instance (deletes its root volume too)
aws ec2 terminate-instances --instance-ids i-0abc123def456789

# Verify: prints "shutting-down", then "terminated"
aws ec2 describe-instances --instance-ids i-0abc123def456789 \
  --query "Reservations[].Instances[].State.Name" --output text

# Once it's terminated, remove the key pair and security group
aws ec2 delete-key-pair --key-name my-aws-key
aws ec2 delete-security-group --group-id sg-0abc123def456789

That needs the AWS CLI configured on your machine. In the console, the same thing is Instances → select the instance → Instance state → Terminate (delete) instance. A terminated instance stays in the list for a while before it disappears; it isn't billed in that state. The security group can only be deleted after the instance is gone. Deleting the key pair in AWS doesn't touch the .pem on your machine, so delete that file too if you won't reuse it. If you allocated an Elastic IP, release it under Network & Security → Elastic IPs, because an unattached Elastic IP is still billed per hour.