How do you configure Uvicorn for production?
Uvicorn is the recommended ASGI server for FastAPI. In development you run it with --reload, but production requires different settings for performance, security, and reliability.
Critical production flags:
| Flag | Dev | Production |
|---|---|---|
--reload |
Yes | Never (file watchers waste CPU) |
--workers |
1 | 2-4 per CPU core |
--host |
127.0.0.1 | 0.0.0.0 (behind reverse proxy) |
--port |
8000 | 8000 (reverse proxy on 80/443) |
--log-level |
debug | info or warning |
--access-log |
Yes | Disable if reverse proxy logs |
--proxy-headers |
No | Yes (behind Nginx/ALB) |
--forwarded-allow-ips |
N/A | Reverse proxy IPs |
Worker count formula:
A common guideline is (2 * CPU_cores) + 1. For I/O-bound apps (most APIs), this works well. For CPU-bound apps, keep workers equal to CPU cores.
Performance tuning:
--loop uvloop: Use uvloop for faster async I/O (Linux only)--http httptools: Use httptools for faster HTTP parsing--limit-concurrency: Limit max concurrent connections--timeout-keep-alive: Default 5s, increase for long-polling
Security:
- Always run behind a reverse proxy (Nginx, Caddy, or cloud load balancer)
- Use
--proxy-headersso Uvicorn trusts X-Forwarded-For headers - Set
--forwarded-allow-ipsto your reverse proxy's IP
# Development
uvicorn app:app --reload --log-level debug
# Production
uvicorn app:app \
--host 0.0.0.0 \
--port 8000 \
--workers 4 \
--loop uvloop \
--http httptools \
--proxy-headers \
--forwarded-allow-ips "172.17.0.1" \
--log-level warning \
--no-access-log
# --- uvicorn_config.py: Programmatic configuration ---
import multiprocessing
import uvicorn
def get_worker_count() -> int:
"""Calculate optimal worker count based on CPU cores."""
cores = multiprocessing.cpu_count()
return (2 * cores) + 1
if __name__ == "__main__":
uvicorn.run(
"app.main:app",
host="0.0.0.0",
port=8000,
workers=get_worker_count(),
loop="uvloop",
http="httptools",
proxy_headers=True,
forwarded_allow_ips="*", # Restrict in production!
log_level="info",
access_log=False,
timeout_keep_alive=5,
limit_concurrency=1000,
limit_max_requests=10000, # Restart worker after N requests
)
# --- app/main.py ---
from fastapi import FastAPI
app = FastAPI(
title="My API",
docs_url=None, # Disable Swagger in production
redoc_url=None, # Disable ReDoc in production
openapi_url=None, # Disable OpenAPI schema in production
)
@app.get("/health")
async def health():
return {"status": "ok"}
# --- Command line production examples ---
# Basic production:
# uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4
# Optimized production (Linux):
# uvicorn app.main:app \
# --host 0.0.0.0 --port 8000 \
# --workers 9 \
# --loop uvloop --http httptools \
# --proxy-headers \
# --forwarded-allow-ips "10.0.0.0/8" \
# --log-level warning --no-access-log \
# --limit-concurrency 1000 \
# --limit-max-requests 10000
# With SSL (direct, no reverse proxy):
# uvicorn app.main:app \
# --host 0.0.0.0 --port 443 \
# --ssl-keyfile /path/to/key.pem \
# --ssl-certfile /path/to/cert.pem
- •Never use --reload in production -- it wastes CPU on file watching
- •Use --workers with (2 * CPU_cores) + 1 as a starting point
- •Always enable --proxy-headers when behind a reverse proxy for correct client IPs
- •Use uvloop and httptools on Linux for significant performance gains
- •uvloop is not available on Windows -- Uvicorn falls back to the default asyncio loop
- •--forwarded-allow-ips='*' trusts all proxies -- restrict to your actual proxy IPs in production
- •Disabling docs_url/redoc_url/openapi_url in production prevents API schema exposure but also breaks interactive testing
- •--limit-max-requests restarts workers after N requests to mitigate memory leaks, but causes brief request drops during restart