TL;DR: nginx is a web server and reverse proxy. As a web server, it serves static files (HTML, CSS, images) directly to visitors. As a reverse proxy, it receives incoming HTTP requests and forwards them to your application server (Node.js, Python, etc.) running on a local port. nginx handles SSL, compression, caching, and rate limiting in front of your app so your app code does not have to.
Why AI Coders Need to Know This
nginx powers about 34% of all websites — more than any other web server. When you ask AI to write a VPS deployment guide for your app, the instructions almost certainly include installing nginx and configuring it. Even when you use Docker Compose, nginx is often the container that receives external traffic.
For vibe coders, nginx is where abstract "deployment" concepts become concrete. Understanding it means:
- You know why your app runs on port 3000 internally but visitors access port 80/443
- You can read the config AI generates and catch mistakes before deploying
- You can debug "502 Bad Gateway" and "504 Gateway Timeout" errors confidently
- You understand how SSL certificates connect to your domain
The Two Jobs nginx Does
Job 1: Static file server
For a simple HTML/CSS/JS website with no backend, nginx serves files directly from a directory:
server {
listen 80;
server_name theaienabledcoder.com;
root /var/www/html; # Where your files live
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
When a visitor requests /about/, nginx looks for /var/www/html/about/index.html and returns it. No application server needed. This is extremely fast — nginx can serve thousands of requests per second for static files.
Job 2: Reverse proxy to your app
For a Node.js, Python, or other backend app:
server {
listen 80;
server_name api.myapp.com;
location / {
proxy_pass http://localhost:3000; # Forward to your app
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
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_cache_bypass $http_upgrade;
}
}
Visitors connect to nginx on port 80. nginx forwards each request to localhost:3000 where your Node.js app is running. Your app responds to nginx, and nginx passes the response back to the visitor. The visitor never knows about port 3000 — they just see api.myapp.com.
Real Scenario
Prompt I Would Type
Write an nginx config for my VPS:
- Domain: myapp.com (redirect www to non-www)
- Serve static files from /var/www/myapp/
- Forward /api/* requests to my Node.js app on localhost:3000
- Set up SSL with Let's Encrypt
- Add security headers
- Enable gzip compression
What AI Generated
# /etc/nginx/sites-available/myapp.com
# Redirect HTTP to HTTPS
server {
listen 80;
server_name myapp.com www.myapp.com;
return 301 https://myapp.com$request_uri;
}
# Redirect www to non-www (HTTPS)
server {
listen 443 ssl;
server_name www.myapp.com;
ssl_certificate /etc/letsencrypt/live/myapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myapp.com/privkey.pem;
return 301 https://myapp.com$request_uri;
}
# Main server block
server {
listen 443 ssl http2;
server_name myapp.com;
ssl_certificate /etc/letsencrypt/live/myapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myapp.com/privkey.pem;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript;
# Static files
root /var/www/myapp;
index index.html;
location / {
try_files $uri $uri/ /index.html; # SPA fallback
}
# Proxy API requests to Node.js
location /api/ {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
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_read_timeout 60s;
}
# Static asset caching
location ~* \.(css|js|png|jpg|webp|svg|ico|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
Understanding Each Part
server blocks
Each server { } block is a virtual host — a separate website or domain. nginx decides which block handles a request based on the server_name and listen port. You can have multiple server blocks in one file or separate files in /etc/nginx/sites-available/.
listen and server_name
listen 80 means this block handles HTTP traffic. listen 443 ssl means HTTPS. server_name myapp.com specifies which domain this block responds to. If a request comes in for a domain not listed in any server block, nginx uses the first block as a default.
location blocks
Location blocks match URL paths and define what to do with them. Common patterns:
location /— matches everything (fallback)location /api/— matches paths starting with /api/location ~* \.js$— matches paths ending with .js (regex, case-insensitive)
nginx uses the most specific matching location block. /api/users matches location /api/ before it matches location /.
try_files
try_files $uri $uri/ /index.html means: try to serve the exact file requested, then try it as a directory, and if neither exists serve /index.html. This is the Single Page Application (SPA) fallback — it lets React Router and similar tools handle routing even when users land directly on deep URLs.
proxy_pass headers
The proxy_set_header lines pass important information from nginx to your app:
X-Real-IP— the visitor's actual IP address (your app sees nginx's IP without this)X-Forwarded-For— the full chain of proxy IPsUpgrade / Connectionheaders — required for WebSocket support
What AI Gets Wrong About nginx
Forgetting to enable the config
AI creates a config file in /etc/nginx/sites-available/ but forgets to enable it. nginx only reads configs in /etc/nginx/sites-enabled/. You must symlink the file:
sudo ln -s /etc/nginx/sites-available/myapp.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo nginx -s reload
SSL certificate path errors
AI hardcodes Let's Encrypt certificate paths like /etc/letsencrypt/live/myapp.com/fullchain.pem. If the certificate does not exist yet (you have not run certbot), nginx fails to start. Run certbot first, then configure SSL.
Port conflicts with the default config
Ubuntu's default nginx install includes a default server block in /etc/nginx/sites-enabled/default that listens on port 80. Your new config also listens on port 80. nginx refuses to start with duplicate listeners. Remove the default: sudo rm /etc/nginx/sites-enabled/default.
502 Bad Gateway — app is not running
The most common nginx error. nginx is running and configured correctly, but your Node.js app is not running on port 3000. nginx cannot connect to it. Fix: start your app. Use PM2 to keep it running: pm2 start app.js.
Missing proxy timeout for slow operations
AI generates nginx configs without custom timeouts. If your API takes more than 60 seconds (default) to respond, nginx returns a 504. Add proxy_read_timeout 300s; for endpoints that do heavy processing.
Always Test Before Reloading
sudo nginx -t validates your config without disrupting live traffic. If this command returns errors, do not reload — your site will go down. Fix the config errors first, then reload.
Essential nginx Commands
sudo nginx -t # Test config (do this before every reload)
sudo nginx -s reload # Graceful reload (no downtime)
sudo systemctl restart nginx # Full restart
sudo systemctl status nginx # Check if nginx is running
sudo tail -f /var/log/nginx/access.log # Watch live requests
sudo tail -f /var/log/nginx/error.log # Watch errors
What to Learn Next
- What Is a VPS? — The server nginx runs on.
- What Is SSH? — How to connect to the server to configure nginx.
- What Is a CDN? — Cloudflare sits in front of nginx for edge caching.
- What Is DNS? — DNS points your domain to your nginx server.
Next Step
If you have a VPS, SSH in and check sudo nginx -t on your current config. Then tail the error log (sudo tail -f /var/log/nginx/error.log) while making a request to see what nginx is actually doing. Reading the logs is the fastest way to understand what is happening at the server level.
FAQ
nginx (pronounced "engine-x") is a high-performance web server and reverse proxy. It can serve static files, forward requests to application servers, handle SSL/TLS, load balance across multiple backends, and cache responses. It powers about 34% of all websites globally.
A reverse proxy sits in front of your application server and forwards client requests to it. When nginx acts as a reverse proxy, users connect to nginx on port 80/443, and nginx forwards requests to your Node.js or Python app on a local port. The visitor only ever sees nginx — the internal app port is hidden.
Run sudo nginx -t. This validates the configuration syntax and checks for errors without reloading. If it returns "test is successful," it is safe to reload with sudo nginx -s reload. Always run this before applying config changes to a live server.
A 502 means nginx is running but cannot connect to your application server. The most common cause: your Node.js or Python app is not running. Start it (and use PM2 to keep it running after server reboots). Also check that the port in proxy_pass matches where your app is actually listening.
First validate: sudo nginx -t. Then graceful reload (no dropped connections): sudo nginx -s reload. For a full restart: sudo systemctl restart nginx. The graceful reload is preferred for production servers.