Deploying With Uvicorn and Nginx
Introduction
uvicorn --reload is for local development only. Production runs Uvicorn workers or Gunicorn with UvicornWorker behind Nginx for HTTPS, static files, and WebSocket upgrades. This chapter deploys a FastAPI app on Linux with 12-factor environment config.
Prerequisites
- FastAPI Security Best Practices
- Settings and Configuration
- Linux server—see Linux track
- Compare Flask deploy: Gunicorn and Nginx
Development vs Production
uvicorn --reload | Production Uvicorn/Gunicorn | |
|---|---|---|
| Purpose | Local dev | Serve real traffic |
| Auto reload | Yes | No |
| Workers | One | Multiple processes |
| Bind | localhost OK | Often 127.0.0.1 behind Nginx |
Warning
Never Use --reload in Production
Reload watches files and is not hardened for public exposure.
Install Production Server
pip install "uvicorn[standard]" gunicornAdd to requirements.txt.
Run Uvicorn Directly
export ENVIRONMENT=production
export SECRET_KEY="your-production-secret"
export DATABASE_URL="mysql+pymysql://user:pass@127.0.0.1/app?charset=utf8mb4"
uvicorn app.main:app --host 127.0.0.1 --port 8000 --workers 4Code explanation:
--workers 4— multiple processes (often2 × CPU + 1starting point)- Bind 127.0.0.1 — Nginx faces the public internet
Gunicorn + UvicornWorker
Recommended by many teams for process management:
gunicorn app.main:app \
-k uvicorn.workers.UvicornWorker \
-w 4 \
-b 127.0.0.1:8000 \
--timeout 120 \
--access-logfile /var/log/fastapi-app/access.log \
--error-logfile /var/log/fastapi-app/error.logCode explanation:
-k uvicorn.workers.UvicornWorkerruns ASGI app under Gunicorn master--timeoutaligns with long uploads—match Nginxproxy_read_timeout
Health Check Route
@app.get("/health")
def health():
return {"status": "ok"}Load balancers probe GET /health—no auth, minimal work.
systemd Service
/etc/systemd/system/fastapi-app.service:
Enable:
sudo systemctl daemon-reload
sudo systemctl enable --now fastapi-appRun migrations before start:
alembic upgrade headNginx Reverse Proxy
/etc/nginx/sites-available/fastapi-app:
WebSocket locations add:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";Details: Nginx reverse proxy, proxy Spring/Node pattern.
HTTPS: Let's Encrypt.
Trust Forwarded Headers
Behind proxy, configure if you need client IP in app:
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
app.add_middleware(ProxyHeadersMiddleware, trusted_hosts=["127.0.0.1"])Only when you control the proxy.
Environment File
/etc/fastapi-app/env:
ENVIRONMENT=production
SECRET_KEY=...
DATABASE_URL=...
CORS_ORIGINS=https://app.example.comDeploy Checklist
- No
--reload - Secrets in env file
-
alembic upgrade head - Gunicorn/Uvicorn on localhost
- Nginx
proxy_set_headerset - HTTPS enabled
- Docs disabled in production if required
FAQ
502 Bad Gateway?
App not running or wrong proxy_pass port—systemctl status fastapi-app.
Workers vs async?
Each worker is a process—async helps within worker; scale workers for CPU cores.
Windows production?
Uvicorn directly; IIS or reverse proxy in front—Linux preferred for this track.
Static files?
Nginx alias faster than StaticFiles in Python.
Zero-downtime deploy?
Blue/green or rolling with two ports—advanced ops.
Log rotation?
logrotate on /var/log/fastapi-app/ or ship to centralized logging.