How to Configure NGINX and Gunicorn for Production Deployment with FastAPI
Deploy FastAPI to production by placing NGINX as a reverse proxy with TLS termination in front of Gunicorn running multiple Uvicorn workers, using the containerized configuration provided in the benavlabs/fastapi-boilerplate repository.
The benavlabs/fastapi-boilerplate repository provides a complete, production-ready stack for deploying FastAPI applications. To configure NGINX and Gunicorn for production deployment, you orchestrate these two components so that NGINX handles client connections, security, and static assets while Gunicorn manages asynchronous worker processes to serve your ASGI application efficiently.
Architecture Overview
Gunicorn with Uvicorn Workers
Gunicorn serves as the primary process manager that spawns multiple Uvicorn workers (uvicorn.workers.UvicornWorker) to handle FastAPI’s ASGI interface. According to the source code in docs/user-guide/production.md, the recommended worker count follows the formula workers = multiprocessing.cpu_count() * 2 + 1, which maximizes concurrency while maintaining a spare worker for graceful restarts.
Setting preload_app = True in your Gunicorn configuration loads the FastAPI application once before forking workers, reducing memory footprint via copy-on-write. Additional protections include max_requests and max_requests_jitter to prevent memory leaks by periodically recycling workers, along with tuned timeout and keepalive values to handle long-running connections safely.
NGINX Reverse Proxy
NGINX sits in front of Gunicorn and terminates client connections, providing capabilities that Gunicorn does not handle efficiently. As implemented in nginx/nginx.conf, NGINX manages:
- TLS termination with
listen 443 ssl http2and strong cipher suites - Security headers including
X-Frame-Options,X-Content-Type-Options, andStrict-Transport-Security - Compression via
gzip onfor text and JSON responses - Rate limiting using
limit_req_zoneandlimit_reqto protect against abuse - Static file serving through dedicated
locationblocks that bypass the application server - Health check endpoints (
/health,/ready) that exclude rate limiting for monitoring systems
The repository provides two configurations: default.conf for simple single-server setups and the full nginx/nginx.conf for load-balanced, HTTPS-enabled deployments.
Configuration Files
Gunicorn Configuration (gunicorn.conf.py)
Create a gunicorn.conf.py file in your project root to centralize production settings. This configuration leverages the Uvicorn worker class for ASGI compatibility and implements memory and process management best practices:
# https://github.com/benavlabs/fastapi-boilerplate/blob/main/docs/user-guide/production.md
import multiprocessing
bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "uvicorn.workers.UvicornWorker"
worker_connections = 1000
max_requests = 1000
max_requests_jitter = 50
preload_app = True
timeout = 30
keepalive = 2
loglevel = "info"
accesslog = "-"
errorlog = "-"
Run Gunicorn with this configuration using:
uv run gunicorn src.app.main:app -c gunicorn.conf.py
NGINX Configuration (nginx.conf)
For production deployments requiring HTTPS and advanced tuning, use the comprehensive configuration located at nginx/nginx.conf. This setup defines an upstream backend and implements security hardening:
# https://github.com/benavlabs/fastapi-boilerplate/blob/main/docs/user-guide/production.md
events { worker_connections 1024; }
http {
upstream fastapi_backend { server web:8000; }
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
gzip on;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/json;
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location / {
limit_req zone=api burst=20 nodelay;
proxy_pass http://fastapi_backend;
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_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
location /health {
proxy_pass http://fastapi_backend;
access_log off;
}
location /ready {
proxy_pass http://fastapi_backend;
access_log off;
}
location /static/ {
alias /code/static/;
expires 1y;
add_header Cache-Control "public, immutable";
}
}
}
Minimal Configuration (default.conf)
For simpler deployments or single-container setups, the repository includes default.conf with a basic reverse proxy configuration:
# https://github.com/benavlabs/fastapi-boilerplate/blob/main/default.conf
server {
listen 80;
location / {
proxy_pass http://web: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;
}
}
Docker Deployment
Production Dockerfile
The production image defined in scripts/production_with_nginx/Dockerfile uses uv for dependency management and launches Gunicorn with Uvicorn workers:
# https://github.com/benavlabs/fastapi-boilerplate/blob/main/scripts/production_with_nginx/Dockerfile
FROM python:3.11-slim
WORKDIR /code
RUN apt-get update && apt-get install -y gcc && rm -rf /var/lib/apt/lists/*
RUN pip install uv
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev
COPY src/ ./src/
RUN useradd --create-home --shell /bin/bash app && chown -R app:app /code
USER app
CMD ["uv", "run", "gunicorn", "src.app.main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000"]
Docker Compose Orchestration
The scripts/production_with_nginx/docker-compose.yml orchestrates the complete stack, linking the FastAPI application behind NGINX with persistent storage and background workers:
# https://github.com/benavlabs/fastapi-boilerplate/blob/main/scripts/production_with_nginx/docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
env_file: ./src/.env
depends_on:
- db
- redis
restart: unless-stopped
deploy:
resources:
limits:
memory: 1G
reservations:
memory: 512M
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/ssl:/etc/nginx/ssl
depends_on:
- web
restart: unless-stopped
db:
image: postgres:15-alpine
env_file: ./src/.env
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
worker:
build: .
command: uv run arq src.app.worker.WorkerSettings
env_file: ./src/.env
depends_on:
- redis
volumes:
postgres_data:
Summary
- Use Gunicorn with Uvicorn workers (
uvicorn.workers.UvicornWorker) to serve FastAPI’s ASGI application in production, calculating worker count ascpu_count * 2 + 1. - Enable
preload_app = Trueto reduce memory usage by loading the application once before forking workers. - Place NGINX in front of Gunicorn to handle TLS termination, security headers, gzip compression, rate limiting, and static file serving.
- Mount SSL certificates into the NGINX container at
/etc/nginx/ssl/and configurelisten 443 ssl http2for HTTPS support. - Define health check endpoints (
/health,/ready) in NGINX without rate limiting to ensure monitoring systems can reach your application. - Use the provided Docker Compose setup in
scripts/production_with_nginx/to orchestrate the web server, reverse proxy, database, and background workers with appropriate resource limits.
Frequently Asked Questions
How many Gunicorn workers should I run in production?
Use the formula multiprocessing.cpu_count() * 2 + 1 as implemented in the example gunicorn.conf.py. This provides two workers per CPU core plus one additional worker to ensure graceful restarts without dropping connections. Adjust based on your application's memory footprint and the workload characteristics observed in monitoring.
Why use NGINX in front of Gunicorn for FastAPI?
NGINX handles concerns that Gunicorn does not optimize for, including TLS termination (offloading cryptographic operations), static file serving (bypassing Python entirely for assets), rate limiting (protecting against brute force attacks), and security headers (adding HSTS, XSS protection, and frame options). This separation of concerns allows Gunicorn to focus solely on executing Python code efficiently.
What is the purpose of preload_app in Gunicorn?
Setting preload_app = True loads your FastAPI application code once in the master process before forking worker processes. This reduces overall memory consumption through copy-on-write semantics, as workers share the same memory pages until they are modified. Without preloading, each worker would load the application independently, significantly increasing memory usage on multi-core servers.
How do I configure SSL certificates for the production deployment?
Place your certificate and private key files in the nginx/ssl/ directory and mount this volume to /etc/nginx/ssl/ in the NGINX container. Reference these files in nginx/nginx.conf using the ssl_certificate and ssl_certificate_key directives. The configuration also enforces modern TLS versions (1.2 and 1.3) and strong cipher suites to ensure secure client connections.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →