1 What is NGINX?

NGINX (say "engine-x") is the traffic director of the web — the first program a request meets when it reaches your server.

One program, three jobs

JobWhat it meansExample
Web serverHand files (HTML, CSS, images) straight to browsers — blisteringly fastyour React build's index.html
Reverse proxyReceive a request, pass it to an app behind it, return the app's answerforward /api/… to a Node app on port 4000
Load balancerSpread requests across several copies of an app3 backends share the traffic

💡 "Reverse" proxy because it hides servers from clients (a normal "forward" proxy hides clients from servers). Users only ever talk to nginx — the apps behind it are invisible.

Why every DevOps engineer meets it

Roughly a third of all websites sit behind nginx — including this very learning portal! When you open academy.brabi.in, an nginx on the server reads the request, terminates HTTPS, and quietly forwards it to the app in a Docker container. In Kubernetes, the standard ingress controller is… nginx again (you saw it in the K8s module). Learn it once, use it everywhere.

2 A Request's Journey

Follow one click from a browser to your app and back. Step through it.

🎬 Try it — walk the request through the system

🧑‍💻 Browserhttps://academy.brabi.in
🌍 DNSname → 203.0.113.10
⚙️ NGINX :443TLS + routing
📦 App :3000builds the page
🧑‍💻 Browserrenders response
Press Next step to begin.

The key idea: two separate conversations

The browser talks to nginx (encrypted, on port 443). nginx then opens its own plain-HTTP conversation with the app (on localhost, port 3000). The app never sees the internet directly — nginx shields it, adds headers, and can cache, compress, limit and log everything in between.

3 Install & the Commands You'll Actually Use

Installation is one line. Daily life is four commands.

# Ubuntu / Debian
sudo apt update && sudo apt install nginx

# is it running?
sudo systemctl status nginx

The 4 daily commands

CommandWhat it doesWhen
sudo nginx -tTest the config for syntax errorsALWAYS before reloading
sudo systemctl reload nginxApply config without dropping connectionsafter every config change
sudo systemctl restart nginxFull stop/start (brief downtime)rarely — reload is almost always enough
sudo tail -f /var/log/nginx/error.logWatch errors livewhen something 502s

💡 Golden habit: nginx -t && systemctl reload nginx — the reload only runs if the test passes. A typo can otherwise take every site on the server down.

4 Config Anatomy

nginx config is just directives (settings ending in ;) grouped into blocks ({ }). Click any line below to decode it.

🎬 Try it — click each line of a real config

server {
    listen 80;
    server_name academy.brabi.in;
    location / {
        proxy_pass http://127.0.0.1:3100;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
👆 click a line

Where the files live (Ubuntu)

PathHolds
/etc/nginx/nginx.confglobal settings; includes the folders below
/etc/nginx/sites-available/one file per website (the library)
/etc/nginx/sites-enabled/symlinks to the sites that are actually ON
/var/log/nginx/access.log + error.log
# turn a site on = create the symlink, test, reload
sudo ln -s /etc/nginx/sites-available/academy /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

💡 $host, $scheme, $remote_addr… — words starting with $ are variables nginx fills in per request.

5 Server Blocks — Many Sites, One Server

One nginx can host dozens of domains on the same IP. The Host header decides which block answers.

🎬 Try it — type a domain, see which block wins

This server has three blocks (a real pattern — it's how one EC2 hosts a main site, a superadmin and an academy):

server { server_name www.brabi.in;        proxy_pass → :8082; }
server { server_name superadmin.brabi.in; proxy_pass → :8080; }
server { server_name academy.brabi.in;    proxy_pass → :3100; }
www.brabi.in→ port 8082
superadmin.brabi.in→ port 8080
academy.brabi.in→ port 3100
Try superadmin.brabi.in, then something unknown like shop.brabi.in.

What if nothing matches?

nginx falls back to the default server — the first block defined, or one marked listen 80 default_server;. Pro move: make the default return 444 (drop) or 404, so random bots scanning your IP get nothing.

6 Location Matching — Which Rule Wins?

Inside a server block, location decides what happens per URL path. The matching ORDER surprises everyone once.

The 4 modifiers

SyntaxMeaningPriority
location = /pingEXACT match only1 — highest
location ^~ /static/prefix; if it's the longest, skip regexes2
location ~ \.php$ / ~*regex (case-sensitive / insensitive), first one wins3
location /api/plain prefix — longest one wins4 — fallback

🎬 Try it — the location referee

Five rules are defined. Type a path and see the referee's decision:

location = /health        ② location ^~ /assets/
③ location ~* \.(png|jpg)$  ④ location /api/
⑤ location /
type a path and press Match…

💡 The classic trap: /assets/logo.png hits ^~ /assets/ (regexes skipped), but /team/photo.png hits the regex ③ — even though prefix ⑤ also matched. Regexes beat plain prefixes.

7 Static Files, SPAs & Caching

Serving files is nginx's superpower — but React/Vue apps need one special line.

A static site (or a built React app)

server {
    listen 80;
    server_name app.example.com;
    root /usr/share/nginx/html;   # folder with index.html
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;  # ← the SPA line
    }

    # hashed assets never change — let browsers keep them for a year
    location ~* \.(js|css|png|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    gzip on;   # compress text responses (~70% smaller)
}

💡 Why try_files? A React Router URL like /portal/settings is not a real file on disk. Without the fallback to /index.html, refreshing that page = 404. With it, nginx serves the app shell and the router takes over. Every SPA deployment needs this line.

root vs alias — the classic confusion

location /img/ { root  /data; }  # /img/cat.png → /data/img/cat.png  (path APPENDED)
location /img/ { alias /data/; } # /img/cat.png → /data/cat.png      (prefix REPLACED)

8 Reverse Proxy — nginx in Front of Your App

The bread-and-butter production setup: nginx owns port 443, your app hides on a local port.

The production-grade proxy block

location / {
    proxy_pass http://127.0.0.1:3000;

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;      # websockets
    proxy_set_header Connection 'upgrade';

    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

🎬 Try it — why those X-Forwarded headers matter

A student at IP 49.43.248.228 logs in. What does the app see?

choose one…

💡 True story from THIS portal: without X-Forwarded-Host, the app built redirect links pointing to 0.0.0.0:3000 — its own container address — instead of the public domain. Behind a proxy, the app only knows what the headers tell it.

9 Load Balancing

When one app copy isn't enough, define an upstream pool and let nginx deal the cards.

upstream api_pool {
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
    server 127.0.0.1:3003;
    # least_conn;   ← or pick the least-busy instead of round-robin
}
server {
    location / { proxy_pass http://api_pool; }
}

🎬 Try it — deal requests to the pool

⚙️ nginx
📦 :3001hits: 0
📦 :3002hits: 0
📦 :3003hits: 0
Round-robin: 1 → 2 → 3 → 1 → … Kill a backend and watch nginx route around it.

10 HTTPS & Certbot

Free certificates from Let's Encrypt, renewed automatically. Two commands, done.

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d academy.example.com

Certbot proves to Let's Encrypt that you control the domain, then edits your server block for you:

server {
    server_name academy.example.com;
    location / { proxy_pass http://127.0.0.1:3000; }

    listen 443 ssl;                                          # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/…/fullchain.pem;  # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/…/privkey.pem; # managed by Certbot
}
server {   # and a port-80 twin that just redirects
    listen 80;
    server_name academy.example.com;
    return 301 https://$host$request_uri;
}
FactDetail
Certificates last90 days
Renewalautomatic — certbot installs a timer; check with sudo certbot renew --dry-run
Requirementthe domain's DNS must already point at your server before you run certbot
TLS terminationnginx decrypts; the hop to your app is plain HTTP on localhost — that's normal

11 Troubleshooting — Reading nginx's Mind

Three error codes tell you 90% of every story.

Codenginx is saying…First thing to check
502 Bad Gateway"I forwarded the request but nothing answered"is the app running? docker ps / curl 127.0.0.1:3000
504 Gateway Timeout"the app answered too slowly"slow DB query? raise proxy_read_timeout only if truly needed
413 Entity Too Large"the upload exceeds my limit"client_max_body_size 10m;
404 on SPA refresh"no such file on disk"the try_files … /index.html line (section 7)

The debug toolbox

sudo nginx -t                             # config valid?
sudo tail -f /var/log/nginx/error.log     # what exactly failed
curl -I http://127.0.0.1:3000/            # is the app up, bypassing nginx?
curl -I https://mysite.com/               # what does the world see?
sudo ss -tlnp | grep :80                  # who is listening on which port

💡 Debug like a plumber: the request flows browser → nginx → app. curl each junction and find the segment where the water stops.

12 Practice Exercises

Do these on any Linux VM (or a docker run -p 8080:80 nginx container). Peek only after trying.

1. Hello nginx install · systemctl

Install nginx and open the server's IP in a browser. You should see the welcome page. Find the file it served.

Show solution
sudo apt install nginx
systemctl status nginx        # active (running)
# the page lives at /var/www/html/index.nginx-debian.html
2. Your first site server block · root

Serve a folder /srv/hello with your own index.html on port 8081.

Show solution
# /etc/nginx/sites-available/hello
server {
    listen 8081;
    root /srv/hello;
    index index.html;
}
# enable + apply
sudo ln -s /etc/nginx/sites-available/hello /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
curl -I http://localhost:8081/
3. Proxy a container proxy_pass · headers

Run docker run -d -p 3000:80 nginxdemos/hello, then make your main site's /demo/ path proxy to it, passing the real client IP.

Show solution
location /demo/ {
    proxy_pass http://127.0.0.1:3000/;   # note trailing / strips /demo
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
4. Cause & fix a 502 debugging

Stop the container from exercise 3. Load /demo/ — observe the 502. Check the error log, then restart the container and confirm recovery.

Show solution
docker stop <id>                          # breaks it
sudo tail -1 /var/log/nginx/error.log     # "connect() failed … Connection refused"
docker start <id>                         # fixed — nginx recovers instantly, no reload needed
5. SPA fallback try_files

Build any React/Vite app, serve its dist/ with nginx, and make a deep route like /settings/profile survive a refresh.

Show solution
location / {
    try_files $uri $uri/ /index.html;
}

13 Cheat Sheet

Everything on one screen.

## daily driver
sudo nginx -t && sudo systemctl reload nginx
sudo tail -f /var/log/nginx/error.log

## minimal reverse proxy
server {
    listen 80;
    server_name app.example.com;
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

## static site / SPA
root /srv/app;  index index.html;
try_files $uri $uri/ /index.html;
gzip on;
client_max_body_size 10m;

## location priority:  = exact  →  ^~ prefix  →  ~ regex  →  plain prefix

## HTTPS
sudo certbot --nginx -d app.example.com
sudo certbot renew --dry-run

## errors:  502 app down · 504 app slow · 413 body too big · SPA-404 missing try_files
🔓 See course prices