22.5. Nginx
Table of Contents
Why Nginx Matters for Backend Developers
Nginx is one of the most popular web servers in the world. For backend developers it usually sits in front of your application server, for example Uvicorn or Gunicorn, and works as a reverse proxy, TLS terminator, static file server, and more.
In this chapter you will learn how Nginx fits into backend architectures and how to read and write basic Nginx configuration. You do not need to become a full Nginx expert, but you must be comfortable enough to:
- Put Nginx in front of a backend app
- Serve static files
- Configure HTTPS
- Understand and debug common issues
Key idea: In production, your backend app usually does not listen directly on port 80 or 443. Instead, Nginx listens on these ports, then forwards requests to your app running on an internal port such as 8000.
Typical Nginx Use Cases in Backends
Reverse proxy in front of an application server
The most common pattern:
- Nginx listens on port 80 (HTTP) and 443 (HTTPS).
- Your backend app server, such as Uvicorn or Gunicorn, listens on port 8000 on the same machine or another internal machine.
- Nginx forwards incoming requests to
http://127.0.0.1:8000or to a private IP.
High level flow:
- Client sends request to
https://api.example.com/users. - Nginx receives the request on port 443.
- Nginx decrypts TLS.
- Nginx forwards a plain HTTP request to your app server on port 8000.
- Backend app processes request and returns response to Nginx.
- Nginx sends response back to client over HTTPS.
Simple diagram:
| Component | Port | Role |
|---|---|---|
| Browser | 443 | Client |
| Nginx | 80/443 | Public entry point, proxy |
| App server | 8000 | Runs your Python backend |
| Database | 5432 | Internal, not exposed |
Serving static files and media
Nginx is very efficient at serving static content such as:
- CSS and JavaScript bundles
- Images and fonts
- Uploaded files (if not offloaded to S3 or similar)
Typical pattern:
- Dynamic API routes are forwarded to your app.
- Static file paths are served directly from disk by Nginx.
This reduces load on your application and improves performance.
Terminating TLS (HTTPS)
Doing TLS in your Python app is possible, but in production it is common to:
- Use Nginx to handle TLS certificates.
- Let your Flask/FastAPI/Django app accept plain HTTP from Nginx.
Benefits:
- Central place to manage certificates.
- Offload CPU cost of TLS from your app.
- Easier configuration of HTTPS for multiple backend services.
How Nginx Configuration Is Structured
Nginx configuration is usually in /etc/nginx/nginx.conf which includes files like /etc/nginx/conf.d/.conf or /etc/nginx/sites-enabled/.
Typical file structure:
user www-data;
worker_processes auto;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
include /etc/nginx/conf.d/*.conf;
}
Inside http you define server blocks. Each server handles one virtual host (for example one domain).
Basic server block
server {
listen 80;
server_name example.com;
location / {
return 200 'Hello from Nginx';
}
}Important parts:
listen 80: listen on port 80.server_name example.com: only handle requests for this domain.location /: define behavior for requests whose path starts with/.
Nginx as a Reverse Proxy for a Backend API
Simple reverse proxy configuration
Example: you have a FastAPI app served by Uvicorn at http://127.0.0.1:8000.
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1: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;
}
}Explanation of key directives:
proxy_pass http://127.0.0.1:8000
Forward the request to your backend app.proxy_set_header Host $host
Preserve the originalHostheader, for exampleapi.example.com. Many apps rely on this.proxy_set_header X-Real-IP $remote_addr
Store the client IP address. Your app can use this for logging or rate limiting.proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for
Standard header listing all proxies that forwarded the request.proxy_set_header X-Forwarded-Proto $scheme
Tell the backend whether the original request used HTTP or HTTPS.
Important rule: When using Nginx as a reverse proxy, always forward the real client IP and the protocol to your backend with X-Real-IP and X-Forwarded-Proto. Without this, your app sees Nginx as the client.
Example: splitting API and docs
Assume:
- Your FastAPI app serves API under
/api. - You want
/docsto be static HTML directly from Nginx.
server {
listen 80;
server_name api.example.com;
# Static docs
location /docs/ {
root /var/www/api-docs;
try_files $uri $uri/ =404;
}
# Everything else goes to backend
location / {
proxy_pass http://127.0.0.1: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;
}
}Requests to:
http://api.example.com/docs/index.htmlcome from/var/www/api-docs/docs/index.html.http://api.example.com/api/usersgo to your app on port 8000.
Serving Static Files with Nginx
Static files can be served with root or alias.
Example directory layout
/var/www/myapp/
static/
css/
main.css
js/
app.js
images/
logo.pngUsing `root`
location /static/ {
root /var/www/myapp;
try_files $uri $uri/ =404;
}This maps:
- URL
/static/css/main.css - To file
/var/www/myapp/static/css/main.css
Because root adds the location path to the root:
- File path =
root+ URL path.
Using `alias`
location /assets/ {
alias /var/www/myapp/static/;
try_files $uri $uri/ =404;
}This maps:
- URL
/assets/css/main.css - To file
/var/www/myapp/static/css/main.css
Because alias replaces the location path with the alias path:
- File path =
alias+ remainder of URL after/assets.
Table summary:
| Directive | URL path example | Config base path | Final file path |
|---|---|---|---|
| root | /static/css/main.css | /var/www/myapp | /var/www/myapp/static/css/main.css |
| alias | /assets/css/main.css | /var/www/myapp/static/ | /var/www/myapp/static/css/main.css |
Rule of thumb:
Use root when your location path matches the directory structure.
Use alias when you want to map a URL path to a completely different directory path.
Basic HTTPS with Nginx
Nginx is often responsible for HTTPS termination.
Minimal HTTPS configuration
Assume you already have certificates:
/etc/letsencrypt/live/api.example.com/fullchain.pem/etc/letsencrypt/live/api.example.com/privkey.pem
server {
listen 80;
server_name api.example.com;
# Redirect all HTTP to HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
# Optional basic security settings
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass http://127.0.0.1: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;
}
}Here:
- First
serverblock listens on port 80 and sends a redirect to HTTPS. - Second
serverblock listens on 443 with SSL and forwards to your backend.
Handling Common Concerns: Timeouts, Uploads, and Buffers
As your API grows, you will need to adjust some defaults.
Timeouts
By default Nginx expects the backend to respond quickly. For slow endpoints, such as large reports, you may need to increase timeouts.
Example:
location /reports/ {
proxy_read_timeout 300s;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_pass http://127.0.0.1:8000;
}proxy_read_timeout: how long Nginx waits for the backend to send data.proxy_connect_timeout: how long to wait for a connection to your backend.proxy_send_timeout: how long to wait while sending data to the backend.
Maximum upload size
If you upload files to your API, Nginx may reject large uploads before the request reaches your app.
server {
listen 443 ssl;
server_name api.example.com;
client_max_body_size 50M;
location /upload/ {
proxy_pass http://127.0.0.1:8000;
}
}client_max_body_size 50Msets maximum request body size to 50 megabytes.
If you see "413 Request Entity Too Large" errors, this directive is usually the fix.
Example: Nginx in Front of a FastAPI App
Assume:
- Domain:
api.example.com. - Backend: FastAPI with Uvicorn listening on
127.0.0.1:8000. - Static files: at
/var/www/myapp/static. - TLS certificates from Let’s Encrypt.
Complete sample configuration:
server {
listen 80;
server_name api.example.com;
# Redirect to HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 20M;
# Serve static files
location /static/ {
root /var/www/myapp;
expires 7d;
add_header Cache-Control "public";
try_files $uri $uri/ =404;
}
# Proxy API requests
location / {
proxy_pass http://127.0.0.1:8000;
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_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
}
}Points to notice:
/static/is served directly by Nginx.- Everything else is sent to FastAPI.
proxy_http_version 1.1and upgrade headers support WebSockets if your app uses them.- A moderate max body size and read timeout are configured.
Testing and Debugging Nginx
Basic commands (Linux)
Common commands on a typical Linux server:
# Test Nginx configuration
sudo nginx -t
# Reload configuration without full restart
sudo systemctl reload nginx
# Restart Nginx service
sudo systemctl restart nginx
# View current status
sudo systemctl status nginx
Always run nginx -t before reloading to catch syntax errors.
Common problems and symptoms
| Symptom | Possible cause |
|---|---|
| Browser shows "Bad Gateway" (502) | Backend app on port 8000 is down or misconfigured |
| "Connection refused" in Nginx error log | Wrong proxy_pass address or port |
| "413 Request Entity Too Large" | client_max_body_size too small |
| Infinite redirect loop from HTTP to HTTPS | Proxying to https:// instead of http:// backend |
Your app sees client IP as 127.0.0.1 | Missing X-Real-IP and X-Forwarded-For headers |
| Static files return 404 | Wrong root or alias path, or wrong URL prefix |
Example logs are usually in /var/log/nginx/access.log and /var/log/nginx/error.log.
How Nginx Fits with Reverse Proxies and Load Balancing
In the broader context of this course:
- Nginx is a reverse proxy, sitting in front of your backend services.
- It can also work as a load balancer among multiple backend instances.
Very simple load balancing example:
upstream backend_app {
server 127.0.0.1:8000;
server 127.0.0.1:8001;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://backend_app;
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;
}
}Here:
upstream backend_appdefines a pool of backend servers.- Nginx will distribute requests between port 8000 and 8001.
You will see more about load balancing in the dedicated load balancing chapter. For now, it is enough to understand that Nginx can proxy to a single backend or to several.
Practical Checklist for Using Nginx with a Backend
When you set up Nginx in front of a backend application, walk through this checklist:
- Server block
- Correct
server_namefor your domain. listen 80andlisten 443 sslif using HTTPS.- Proxy to backend
proxy_passpoints to the right address and port.proxy_set_headerlines forward important headers.proxy_read_timeoutis appropriate for your slowest endpoint.- Static files
- Use
rootoraliascorrectly. - Paths on disk match URL paths.
- HTTPS
- Certificates exist and paths are correct.
- HTTP redirected to HTTPS.
- TLS versions reasonably secure.
- Uploads and timeouts
client_max_body_sizefits your largest upload.- Timeouts keep long tasks working, but not infinite.
- Testing
nginx -tbefore reload.- Check error logs if requests fail.
If you can do these items, you can operate Nginx effectively as a backend developer.
Views: 8
KAHIBARO