Nginx for Developers: The Config Guide You'll Actually Bookmark
Nginx configs look like hieroglyphics until you understand the mental model. This guide explains what every directive actually does, with copy-paste configs for the 5 most common setups.
The Mental Model: Nginx Is a Mail Room
Think of Nginx as a mail room. Requests arrive at the front desk (port 80/443). The mail room checks the address label (server_name) to route it to the right department (server block). Within the department, it checks the subject line (location) to route it to the right person (upstream/file).
That's it. Everything in Nginx is: match a request → do something with it.
Config 1: Next.js + Django Behind Nginx (Most Common)
# Upstream blocks: define your app servers
upstream frontend {
server frontend_container:3000;
keepalive 16; # reuse connections (huge perf win)
}
upstream backend {
server backend_container:8000;
keepalive 8;
}
server {
listen 443 ssl;
server_name yourapp.com;
# SSL (Let's Encrypt)
ssl_certificate /etc/letsencrypt/live/yourapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourapp.com/privkey.pem;
# API routes -> Django
location /api/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection ""; # required for keepalive
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Admin -> Django
location /admin/ {
proxy_pass http://backend;
proxy_set_header Host $host;
}
# Static files -> Django (with caching)
location /static/ {
proxy_pass http://backend;
expires 30d;
add_header Cache-Control "public, immutable";
}
# Everything else -> Next.js
location / {
proxy_pass http://frontend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_buffering off; # important for streaming SSR
}
}Config 2: HTTPS Redirect + www Canonical
Two things every production site needs: force HTTPS and pick one canonical domain (www or non-www). Without this, Google sees two separate sites splitting your SEO.
# Redirect HTTP -> HTTPS
server {
listen 80;
server_name yourapp.com www.yourapp.com;
return 301 https://yourapp.com$request_uri;
}
# Redirect www -> non-www
server {
listen 443 ssl;
server_name www.yourapp.com;
ssl_certificate /etc/letsencrypt/live/yourapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourapp.com/privkey.pem;
return 301 https://yourapp.com$request_uri;
}
# Main site (canonical)
server {
listen 443 ssl;
server_name yourapp.com;
# ... your config
}The 5 Directives You Must Understand
| Directive | What It Does | Why It Matters |
|---|---|---|
| proxy_http_version 1.1 | Uses HTTP/1.1 for upstream connections | Required for keepalive to work |
| proxy_set_header Connection "" | Clears the Connection header | Without this, keepalive is disabled |
| proxy_buffering off | Streams response directly to client | Needed for SSR streaming, SSE, real-time |
| client_max_body_size 20M | Max upload size | Default is 1MB - file uploads will fail |
| proxy_set_header X-Forwarded-Proto $scheme | Tells backend if request was HTTPS | Django needs this for CSRF and redirects |
Debugging: When Things Go Wrong
502 Bad Gateway: Nginx can't connect to your upstream. Your app container is not running, or it's on a different Docker network.
504 Gateway Timeout: Your app is too slow. Increase proxy_read_timeout or optimize your app.
413 Request Entity Too Large: Upload exceeds client_max_body_size. Increase it.
301 redirect loop: Your app is redirecting to HTTPS, but X-Forwarded-Proto isn't set, so it always thinks it's HTTP.
# Quick debugging commands
nginx -t # test config syntax
nginx -s reload # reload without downtime
tail -f /var/log/nginx/error.log # watch errors live
curl -I https://yourapp.com # check response headersNginx is the bouncer at your club. It decides who gets in, where they go, and how fast. Spend 30 minutes understanding it and you'll save 30 hours debugging production issues.
— alokknight Engineering
