Nginx for Dummies — From First Principles
I'm not an nginx expert. I'm a developer who had to figure it out because I built a FastAPI app and needed something in front of it. Configs looked like gibberish at first. Here's what I learned.
What Problem Does Nginx Solve?
The old way (Apache) created a new process for each visitor. Fine for 10 people. Bad for 10,000 — each process takes memory, and the OS spends more time switching between them than serving content.
Nginx handles many connections in one process using an event loop. Same pattern as Node.js and Redis. One thread, non-blocking I/O, handles thousands of connections.
A Simple FastAPI App
Here's the app I was trying to put nginx in front of:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def home():
return {"message": "hello world"}
@app.get("/api/users")
def get_users():
return [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"},
]Run it with uvicorn main:app --port 8000. It works fine on its own. But if I point a domain at it, I'd need it to handle SSL, serve static files, and deal with slow clients. That's where nginx comes in.
The Config Explained
Nginx configs look scary but they're simple once you break them down.
events {
worker_connections 1024;
}This sets how many connections each worker handles at once. With 4 workers and 1024 each, you can handle 4096 concurrent connections. I keep mine at 1024.
The main block for web stuff:
http {
server {
listen 80;
server_name myapp.com;
location / {
proxy_pass http://localhost:8000;
}
}
}http { }— everything inside is for HTTP trafficserver { }— defines a website. You can have multiple for different domainslisten 80;— port 80 is HTTP.listen 443 ssl;for HTTPSserver_name myapp.com;— which domain this block handleslocation / { }— matches URL paths./catches everything./api/only matches paths starting with /api/proxy_pass http://localhost:8000;— forwards requests to your app
That's the core idea. Everything else is built on this.
Putting Nginx in Front of FastAPI
Here's what I actually use for my FastAPI app:
server {
listen 80;
server_name myapp.com;
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /static/ {
alias /var/www/myapp/static/;
expires 30d;
}
}Requests to / go to FastAPI. Requests to /static/ are served directly by nginx — FastAPI never sees them.
The proxy_set_header lines are important. Without them, FastAPI sees requests coming from 127.0.0.1 (nginx's address) and doesn't know the real client IP. My app needs the real IP for logging, rate limiting, etc.
SSL Setup
I use Let's Encrypt for certs. Here's the nginx config:
server {
listen 443 ssl;
server_name myapp.com;
ssl_certificate /etc/letsencrypt/live/myapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myapp.com/privkey.pem;
location / {
proxy_pass http://localhost:8000;
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;
}
}
server {
listen 80;
server_name myapp.com;
return 301 https://$server_name$request_uri;
}First block handles HTTPS. Second block redirects HTTP to HTTPS. FastAPI never deals with encryption — nginx handles all of it.
Multiple APIs on One Server
I have a FastAPI backend and a separate API on the same server. Nginx routes based on the URL path:
server {
listen 80;
server_name myapp.com;
location /api/ {
proxy_pass http://localhost:8000;
}
location /admin/ {
proxy_pass http://localhost:5000;
}
location / {
proxy_pass http://localhost:3000;
}
}Requests to /api/ go to FastAPI (port 8000). /admin/ goes to another app (port 5000). Everything else goes to the frontend (port 3000). This is how I run multiple apps on one domain.
Things I Kept Getting Wrong
Forgetting proxy_set_header — spent an hour wondering why my app didn't know the client's IP. Fix:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;root vs alias — root appends the URL path, alias replaces it.
location /static/ {
root /var/www; # serves /var/www/static/file.css
}
location /static/ {
alias /var/www/files; # serves /var/www/files/file.css
}I still have to think about this every time.
Not testing config — nginx -t checks your syntax. Run it before reloading. I broke my site once by making a typo and reloading without checking.
Worker count — set it to your CPU cores, not higher. More workers than cores means context switching, which is the opposite of what nginx was designed for.
What I Still Don't Fully Get
Load balancing with upstream blocks — I understand the idea but haven't needed it yet. From what I gather:
upstream backend {
server app1.internal:8000 weight=3;
server app2.internal:8000;
server app3.internal:8000 backup;
}
server {
location / {
proxy_pass http://backend;
}
}This distributes traffic across servers. Round-robin by default. You can set weights, backups, sticky sessions. Makes sense in theory.
Caching and rate limiting — I've read about proxy_cache and limit_req but haven't set them up. They're on my list.
Should You Use Nginx?
If you have a web app facing the internet, yeah probably. It handles SSL, serves static files, buffers slow clients, and routes requests. Your app can focus on your code instead of infrastructure.
I'm still learning. This is just what I've figured out so far from actually using it with FastAPI.