FastAPI Production Server with Gunicorn

The command uvicorn main:app --reload suits a laptop. A real website faces different demands: traffic spikes, crashed processes, and server restarts. A single Uvicorn process uses one CPU core and stops completely when it crashes. Gunicorn manages a group of Uvicorn workers, restarts any worker that fails, and spreads incoming requests across all of them. Picture a shop manager who hires several cashiers and replaces anyone who leaves mid-shift.

The Process Layout

             Internet
                 │
                 ▼
        ┌─────────────────┐
        │  Nginx (proxy)  │   handles HTTPS and static files
        └────────┬────────┘
                 ▼
        ┌─────────────────┐
        │ Gunicorn master │   starts, watches, and restarts workers
        └────────┬────────┘
       ┌─────────┼─────────┐
       ▼         ▼         ▼
  ┌────────┐ ┌────────┐ ┌────────┐
  │Worker 1│ │Worker 2│ │Worker 3│   each runs your FastAPI app
  └────────┘ └────────┘ └────────┘

Installing Gunicorn

pip install gunicorn uvicorn

Starting the Server

gunicorn main:app \
  --worker-class uvicorn.workers.UvicornWorker \
  --workers 4 \
  --bind 0.0.0.0:8000
  • main:app names the file and the app object, exactly as in Uvicorn.
  • --worker-class tells Gunicorn to run each worker with Uvicorn, which adds async support.
  • --workers 4 starts four processes.
  • --bind sets the address and port.

Choosing the Number of Workers

A common starting point equals the number of CPU cores. A server with four cores begins with four workers. Measure real traffic and adjust. Each worker loads a full copy of your app, so memory use grows with the count.

CPU cores   Starting workers   Memory (if one worker uses 150 MB)
─────────   ────────────────   ──────────────────────────────────
    2              2                       about 300 MB
    4              4                       about 600 MB
    8              8                       about 1.2 GB

A Configuration File

Long commands grow hard to read. Move the options into a file named gunicorn.conf.py.

bind = "0.0.0.0:8000"
workers = 4
worker_class = "uvicorn.workers.UvicornWorker"
timeout = 60
graceful_timeout = 30
keepalive = 5
accesslog = "-"
errorlog = "-"

Start the server with a short command:

gunicorn main:app -c gunicorn.conf.py

The dash value sends logs to the terminal, where Docker and system services collect them.

Other Ways to Run Multiple Workers

Uvicorn alone starts several workers.

uvicorn main:app --workers 4 --host 0.0.0.0 --port 8000

The FastAPI command line tool offers a similar option.

fastapi run --workers 4

Gunicorn adds stronger process supervision, which many teams still prefer on traditional servers. Container platforms such as Kubernetes often restart whole containers instead, so a single Uvicorn process per container works well there.

Running Behind Nginx

Nginx faces the internet and forwards requests to Gunicorn. Nginx handles HTTPS certificates and slow clients, and your app stays focused on logic. Tell Gunicorn to trust the forwarded headers so your app sees the real client address.

gunicorn main:app -c gunicorn.conf.py --forwarded-allow-ips="127.0.0.1"

Keeping the Server Running with systemd

A service file starts Gunicorn at boot and restarts it after failures.

[Unit]
Description=FastAPI application
After=network.target

[Service]
User=appuser
WorkingDirectory=/srv/myapp
ExecStart=/srv/myapp/venv/bin/gunicorn main:app -c gunicorn.conf.py
Restart=always

[Install]
WantedBy=multi-user.target
sudo systemctl enable myapp
sudo systemctl start myapp
sudo systemctl status myapp

Graceful Reloads

Sending the HUP signal tells Gunicorn to start fresh workers with new code and retire the old ones after they finish current requests. Visitors never see an error during a deploy.

kill -HUP <gunicorn master process id>

Common Mistakes

Leaving --reload On

The reload option watches files and wastes resources. Use it on laptops only.

Too Many Workers

Workers beyond the CPU count compete for the same cores and eat memory. Start small and measure.

Running as the Root User

A dedicated low-privilege user limits the damage if someone exploits a bug.

Per-Worker State

Each worker keeps its own memory. A Python dictionary used as a cache holds different data in every worker. Use Redis or a database for shared state.

A Deployment Checklist

Run through a short checklist before you announce a launch.

  • Confirm that debug mode and the reload option stay off.
  • Load secrets from environment variables and keep them out of the repository.
  • Test the service file by rebooting the server once and watching the app return on its own.
  • Send a burst of requests with a load testing tool and watch memory and CPU.
  • Set up log rotation so log files never fill the disk.

Each item takes minutes and prevents a common midnight emergency.

Key Points

  • Gunicorn manages several Uvicorn workers and restarts failed ones.
  • Use uvicorn.workers.UvicornWorker as the worker class.
  • Begin with one worker per CPU core and tune from there.
  • Place Nginx in front for HTTPS and slow clients.
  • Keep shared data in Redis or a database, never in worker memory.