Nginx Deep Dive β€” How It Really Works

The Secret Sauce: Event-Driven Architecture

Most old-school web servers (like Apache) work like this:

Request comes in β†’ Create a new thread/process β†’ Handle request β†’ Kill thread

Imagine a restaurant where every customer gets their own personal chef. If 100 customers arrive, you need 100 chefs. Expensive and wasteful.

Nginx does it differently:

One chef (single thread) β†’ Takes orders from ALL customers β†’ Cooks one by one β†’ Serves

This is called event-driven, asynchronous architecture. One worker process handles thousands of connections simultaneously using non-blocking I/O.

Why this matters for your 2GB VPS

Server100 connections1000 connections
Apache~100 threads = ~500MB RAM❌ Would crash
Nginx1-2 worker processes = ~3MB RAMβœ… Still sipping coffee

Workers & Master Process

When nginx starts, it creates:

nginx (master)          ← The manager
  β”œβ”€β”€ worker (CPU 0)   ← Does the actual work
  β”œβ”€β”€ worker (CPU 1)   ← If multi-core
  └── worker (CPU 2)   ← Another one
  • Master process: Reads config, spawns workers, never handles requests
  • Worker processes: Each handles thousands of connections
  • Best setting: One worker per CPU core (auto-detected)

How a Request Flows Through Nginx

Browser β†’ http://43.133.130.60:8080/notes/home
                          β”‚
                    [Network arrives]
                          β”‚
                    [Nginx master passes to worker]
                          β”‚
                    [Worker reads config]
                          β”‚
                    β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”
                    β”‚ location / β”‚
                    β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
                          β”‚
                    try_files:
                    β”œβ”€β”€ /notes/home    β†’ ❌ No such file
                    β”œβ”€β”€ /notes/home.html β†’ βœ… FOUND!
                    └── /notes/home/   β†’ (skipped)
                          β”‚
                    [Read file from disk]
                          β”‚
                    [Add headers (Content-Type, Cache-Control)]
                          β”‚
                    [Send response back to browser]
                          β”‚
                    [Log to access.log]
                          β”‚
                    [Back to listening for next request]

All of this happens in microseconds.

The Config Explained Line by Line

server {
    listen 8080;

listen 8080 β€” binds to TCP port 8080. listen 443 ssl for HTTPS. listen 80 default_server for the default site.

    server_name _;

_ is a catch-all β€” respond to any domain name. Normally you’d put server_name example.com to match specific domains.

    root /home/ubuntu/quartz-site/public;

Every path gets prepended with this. So /notes/home.html becomes /home/ubuntu/quartz-site/public/notes/home.html.

    location = / {
        return 301 /notes/;
    }
  • location = / β€” exact match for / only (the = means β€œexactly this”)
  • return 301 β€” permanent redirect to /notes/
    location / {
        try_files $uri $uri.html $uri/ =404;
    }
  • location / β€” matches everything (prefix match)
  • $uri β€” the requested path (e.g., /notes/home)
  • try_files β€” try each option in order, first match wins

Location Matching Rules

Nginx has a specific priority for locations:

SyntaxExamplePriority
= /path= /1st (exact)
^~ /path^~ /notes/2nd (prefix, no regex)
~ pattern~ \.php$3rd (regex, case-sensitive)
~* pattern~* \.(css|js)$4th (regex, case-insensitive)
/path/5th (regular prefix)

Cache Control Explained

location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|webp)$ {
    expires 7d;
    add_header Cache-Control "public, immutable";
}

When your phone browser loads the garden:

  1. First visit: downloads all CSS, JS, images
  2. Next visit: browser checks β€œhas anything changed?”
  3. With immutable: browser says β€œthis file will NEVER change in 7 days, don’t even ask”

Result: Second page load is instant πŸš€

Useful Nginx Commands

# Check config syntax
sudo nginx -t
 
# Reload config (zero-downtime)
sudo systemctl reload nginx
 
# Restart nginx
sudo systemctl restart nginx
 
# View error logs
sudo tail -f /var/log/nginx/error.log
 
# View access logs
sudo tail -f /var/log/nginx/access.log
 
# Test a config without applying
sudo nginx -t -c /path/to/config.conf
 
# Show compiled modules
nginx -V

Common Mistakes & Fixes

”502 Bad Gateway”

Nginx can’t reach the backend (Node.js, PHP, etc.). Since we serve static files, this shouldn’t happen.

”404 Not Found”

File doesn’t exist at the path. Check: is the root correct? Does the file exist?

β€œ403 Forbidden”

Permission issue. Nginx runs as www-data user. Fix:

sudo chmod +rx /home/ubuntu/quartz-site/public
sudo chown -R www-data:www-data /home/ubuntu/quartz-site/public

Port already in use

sudo ss -tlnp | grep 8080   # See what's using port 8080

Reverse Proxy β€” The Next Level

For future reference, if you ever run a Node.js app on port 3000 and want nginx to handle it:

location /api/ {
    proxy_pass http://localhost:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
}

Now visitors hit port 80/443 β†’ nginx forwards to your app on port 3000. Clean and secure.


πŸ“ Back to Research