“403 errors on a freshly configured Nginx server are almost never about the file. They’re about the path to the file.”

This is a configuration and troubleshooting reference for running Nginx on Ubuntu 22.04 on AWS EC2 — covering the full setup, the security group configuration that catches people out, and the complete 403 debugging chain. Written from a real configuration session where the permission issues took longer than they should have.


Architecture#

Internet (HTTP/80)
    ↓
AWS Security Group — inbound rules
    ↓
Ubuntu 22.04 + Nginx
    ↓
Custom content at /var/www/html

Simple stack. The failure modes are in the details.


EC2 Instance Setup#

AMI: Ubuntu Server 22.04 LTS
Instance: t3.micro
Storage: 8GB SSD

Check your AMI selection before launching. Ubuntu 20.04 and 22.04 are both available and easy to pick the wrong one. The difference matters for package versions and PHP-FPM socket paths if you’re running a full LEMP stack later.

Security Group#

This is the first place Nginx setups fail — the server is configured correctly but traffic never reaches it.

Inbound:
  HTTP  | Port 80  | 0.0.0.0/0
  SSH   | Port 22  | Your IP only

Outbound:
  All traffic (default)

SSH restricted to your IP only. HTTP open to the world. If you’re getting connection timeouts and Nginx is running, check this before anything else — a missing HTTP inbound rule is the most common cause.


Installing Nginx#

sudo apt update
sudo apt install nginx -y
sudo systemctl start nginx
sudo systemctl enable nginx
sudo systemctl status nginx

Visit the server’s public IP. You should see the Nginx default page. If you don’t, and there’s no connection error, the security group is the first thing to check. If there’s a connection refused, Nginx isn’t running — check systemctl status nginx and journalctl -u nginx -n 50.


Custom Content and the 403 Chain#

Replace the default page:

sudo nano /var/www/html/index.html
<!DOCTYPE html>
<html>
<head>
    <title>Nginx — Production Ready</title>
</head>
<body>
    <h1>Nginx configured.</h1>
</body>
</html>

Reload. If you get a 403, work through this chain in order.

403 Debugging Chain#

Step 1 — Check the error log first:

sudo tail -f /var/log/nginx/error.log

The log will tell you exactly what failed. Read it before trying anything else.

Step 2 — Check file ownership:

ls -la /var/www/html/

Nginx runs as www-data. Files owned by root with no read permission for others will 403.

sudo chown -R www-data:www-data /var/www/html

Step 3 — Check file permissions:

# Files should be 644
sudo find /var/www/html -type f -exec chmod 644 {} \;

# Directories should be 755
sudo find /var/www/html -type d -exec chmod 755 {} \;

Step 4 — Check the full path, not just the target:

This is where most people get stuck. Nginx needs execute permission on every directory in the path to traverse it. A 700 or 750 on /var/www blocks access to /var/www/html regardless of what permissions the html directory or its files have.

# Check every component of the path at once
namei -l /var/www/html/index.html

namei -l shows permissions on each path component. If any directory in the chain is missing execute for www-data or others, that’s your 403.

# Fix traversal permissions on the path
sudo chmod o+x /var/www
sudo chmod 755 /var/www/html

The full correct permission setup:

sudo chown -R www-data:www-data /var/www/html
sudo find /var/www -type d -exec chmod 755 {} \;
sudo find /var/www -type f -exec chmod 644 {} \;

After each change:

sudo nginx -t && sudo systemctl reload nginx

Always test config before reload. A broken config on reload takes the server down.


Other Common Failure Modes#

Nginx won’t start:

sudo nginx -t
# Fix whatever it reports
sudo systemctl restart nginx

nginx -t validates the config and tells you exactly what’s wrong. Run it before every restart.

Port 80 already in use:

sudo netstat -tulpn | grep :80
# If Apache is running:
sudo systemctl stop apache2
sudo systemctl disable apache2

Ubuntu 22.04 doesn’t ship Apache by default, but if you’ve been on this instance for a while it may be installed.

SELinux blocking access (CentOS/RHEL only, not Ubuntu):

sestatus
# If enforcing:
sudo chcon -Rt httpd_sys_content_t /var/www/html

Not applicable on Ubuntu, but worth knowing if you’re working across distros.


Production Hardening#

The setup above serves content. For production, add:

server {
    listen 80;
    server_name your-domain.com;

    # Don't advertise Nginx version
    server_tokens off;

    # No directory listings
    autoindex off;

    root /var/www/html;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    # Block access to hidden files
    location ~ /\. {
        deny all;
    }
}

server_tokens off stops Nginx from including its version number in response headers and error pages. autoindex off prevents directory listing if there’s no index file. Both are one-liners with no downside.

For SSL — run Certbot and then tighten the TLS config beyond what Certbot sets by default. Certbot’s defaults get you a B on SSL Labs. The full TLS hardening process is covered in the LEMP stack hardening post.


Validation#

# Confirm Nginx is running
sudo systemctl status nginx

# Test response
curl -I http://localhost

# Check from outside (replace with your IP/domain)
curl -I http://your-server-ip

Expected response headers:

HTTP/1.1 200 OK
Server: nginx
Content-Type: text/html

Note Server: nginx with no version number — that’s server_tokens off working.


What I’d Do Differently#

Use Ansible from the start. This entire setup — package installation, directory creation, permission setting, config templating — is four Ansible tasks. Doing it manually is fine for understanding what’s happening. Doing it manually in production means you can’t reproduce it reliably.

namei -l first. Whenever permissions are involved, run namei -l on the full path before trying anything else. It shows you exactly where the chain breaks instead of you working through it trial-and-error.


Source#

Configuration files on GitHub .


Tags#

#Infrastructure #Linux #Nginx #AWS #EC2 #Security


About the Author#

Elijah Udom (elijahu) is an Infrastructure & Cloud Engineer based in Lagos, Nigeria. AWS, Kubernetes, eBPF security, AI/ML infrastructure. Building in the open.

Elijah Udom


Next: AWS Security Group Auditor →