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 http2 and strong cipher suites
  • Security headers including X-Frame-Options, X-Content-Type-Options, and Strict-Transport-Security
  • Compression via gzip on for text and JSON responses
  • Rate limiting using limit_req_zone and limit_req to protect against abuse
  • Static file serving through dedicated location blocks 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 as cpu_count * 2 + 1.
  • Enable preload_app = True to 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 configure listen 443 ssl http2 for 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →