← Back to writing

Deploying a Static Website on AWS EC2 with Nginx

Launch an Ubuntu EC2 instance, secure SSH access, install Nginx, and serve a static site while understanding each network layer involved.

Static websites are often better served by object storage or an edge platform, but deploying one on EC2 is an excellent infrastructure exercise. It makes the full request path visible: internet traffic reaches an AWS security group, enters an Ubuntu virtual machine, is accepted by Nginx, and becomes an HTML response.

This walkthrough is based on my ec2-tutorial repository.

The architecture

Browser
   |
   | HTTP :80
   v
AWS security group
   |
   v
Ubuntu EC2 instance
   |
   v
Nginx -> /var/www/html/index.html

Each layer can independently block a request. Understanding that separation makes troubleshooting much faster.

1. Launch the instance

From the EC2 console, create an instance with an Ubuntu Server image. A small instance type is enough for the exercise, but verify current AWS pricing and Free Tier eligibility in your own account rather than assuming a named type is free.

Configure:

  • A clear instance name such as static-website
  • A VPC and subnet with internet connectivity
  • Auto-assigned public IPv4 if you are not using an Elastic IP
  • A new or existing SSH key pair
  • An attached security group

The public IP can change when a non-Elastic-IP instance is stopped and started. Use an Elastic IP or DNS when the address must remain stable, and remember that those resources can affect cost.

2. Apply narrow network rules

The security group should allow:

PurposeProtocolPortSource
SSH administrationTCP22Your current public IP only
Website trafficTCP800.0.0.0/0 and optionally ::/0

Avoid opening SSH to the entire internet unless there is a deliberate reason and additional protection. Security groups are stateful, so response traffic for allowed inbound connections is handled automatically.

3. Protect the private key

On Linux or macOS, restrict the downloaded key before using it:

chmod 400 aws-key.pem
ssh -i aws-key.pem ubuntu@YOUR_PUBLIC_IP

A .pem private key is a credential. Store it outside the project, add *.pem to the project’s .gitignore, and never commit it—even to a private repository. If a real private key has ever entered Git history, deleting the file in a later commit is not enough: replace the EC2 key or remove its public counterpart from authorized_keys, then purge the exposed secret from history if appropriate.

The Ubuntu AMI’s default login user is normally ubuntu; other images use different usernames.

4. Install and verify Nginx

After connecting, update the package metadata and installed software:

sudo apt update
sudo apt upgrade -y
sudo apt install nginx -y

Start Nginx now and on future boots:

sudo systemctl enable --now nginx
sudo systemctl status nginx

Open http://YOUR_PUBLIC_IP in a browser. The default Nginx page confirms four things at once:

  1. The instance is running.
  2. Its public network path works.
  3. The security group permits port 80.
  4. Nginx is accepting and serving requests.

5. Deploy the page

Nginx’s default Ubuntu site serves /var/www/html. Replace the default document with your own index.html:

cd /var/www/html
sudo mv index.nginx-debian.html index.nginx-debian.html.backup
sudo nano index.html

A minimal page is enough to prove the deployment:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>My AWS Website</title>
  </head>
  <body>
    <h1>Hello from AWS EC2</h1>
    <p>This page is served by Nginx on Ubuntu.</p>
  </body>
</html>

Refresh the public IP. Nginx serves the new file immediately; no application process or restart is required for a static HTML change.

For a multi-file site, build locally and transfer the output with rsync, scp, a CI workflow, or configuration management rather than editing production files by hand.

Troubleshooting by layer

SSH fails

  • Confirm the instance is running and has a public IP.
  • Confirm port 22 is allowed from your current IP.
  • Confirm the AMI username and private-key path.
  • Use ssh -vvv to inspect negotiation.

The website times out

  • Confirm inbound TCP 80 is allowed.
  • Check sudo systemctl status nginx.
  • Check the host firewall with sudo ufw status.
  • Inspect sudo journalctl -u nginx and /var/log/nginx/error.log.

The website returns 403 or permission errors

Check directory traversal and file-read permissions. On a default setup, static files should normally be readable by Nginx without making everything writable by www-data.

Production improvements

The educational deployment stops at HTTP on a public IP. A production path should add a domain, HTTPS, automated deployment, backups, monitoring, patching, and a cost plan. Depending on the application, a static hosting platform may provide all of that with less server administration.

The value of this EC2 exercise is seeing every layer. Once that path is understood, choosing a simpler managed service becomes an engineering decision rather than magic.

See the complete original walkthrough in the ec2-tutorial repository.