# How to Configure NGINX and Gunicorn for Production Deployment with FastAPI

> Learn to deploy FastAPI to production with NGINX & Gunicorn. Implement TLS termination and multiple Uvicorn workers using the benavlabs fastapi-boilerplate containerized setup for optimal performance.

- Repository: [Benav Labs/fastapi-boilerplate](https://github.com/benavlabs/fastapi-boilerplate)
- Tags: how-to-guide
- Published: 2026-02-26

---

**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`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/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`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/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`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/default.conf) for simple single-server setups and the full [`nginx/nginx.conf`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/nginx/nginx.conf) for load-balanced, HTTPS-enabled deployments.

## Configuration Files

### Gunicorn Configuration (gunicorn.conf.py)

Create a [`gunicorn.conf.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/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:

```python

# 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:

```bash
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`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/nginx/nginx.conf). This setup defines an upstream backend and implements security hardening:

```nginx

# 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`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/default.conf) with a basic reverse proxy configuration:

```nginx

# 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:

```dockerfile

# 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`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/scripts/production_with_nginx/docker-compose.yml) orchestrates the complete stack, linking the FastAPI application behind NGINX with persistent storage and background workers:

```yaml

# 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`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/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`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/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.