FastAPI Uvicorn Nginx Production: Gunicorn Workers, SSL, and Systemd for AI APIs in 2026
TL;DR — FastAPI uvicorn nginx production: Gunicorn, systemd, SSL. Reflex: "Gunicorn process manager + UvicornWorker. --workers 4, --timeout 120, --max-requests 1000, --graceful-timeout 30. systemd for restarts, Nginx for SSL. Unix socket. HUP for graceful reload." QubitLogic: "Production: Gunicorn + UvicornWorker, systemd restart, Nginx TLS. 2 workers start, 1 per 512MB-1GB. proxy_read_timeout 120s (300s for AI). uvicorn alone = no multi-worker." OneUptime: "Gunicorn + Uvicorn for single server. Docker + Gunicorn for containers. Nginx for SSL, load balancing, WebSocket. Health checks: liveness + readiness." VengalaVinay: "Gunicorn master + Uvicorn workers. cpu_count()*2+1 for I/O-bound. max_requests for memory leaks. async/await throughout." Learn more with FastAPI tutorial, Docker deployment, async backend, and monitoring.
Reflex frames the production stack: "Uvicorn is an ASGI server. Gunicorn is a process manager that can spawn and supervise multiple Uvicorn workers. In production, you almost always want both."
QubitLogic adds the contrast: "Running uvicorn main:app --reload on your laptop is development. Production means Gunicorn managing Uvicorn workers, systemd restarting on crash, Nginx terminating TLS, and Let's Encrypt for free HTTPS."
Production Architecture
HTTPS 443
WSS for WebSocket"] end subgraph Nginx["Nginx Reverse Proxy"] SSL["SSL Termination
Let's Encrypt Certbot
TLS 1.3
cert.pem + key.pem"] Proxy["Reverse Proxy
proxy_pass to socket
proxy_buffering on
X-Forwarded headers"] WS["WebSocket Support
proxy_http_version 1.1
Upgrade header
Connection upgrade
proxy_read_timeout 300s"] Static["Static Files
serve directly
offload from Gunicorn
gzip compression"] RateLimit["Rate Limiting
limit_req_zone
per-IP throttling
429 on excess"] end subgraph Systemd["Systemd Process Manager"] Service["Service Unit
ExecStart gunicorn
Restart=always
RestartSec=3
User=appuser"] Security["Security Hardening
PrivateTmp=true
ProtectSystem=strict
EnvironmentFile
No secrets in unit"] Reload["Graceful Reload
kill -HUP to master
workers finish requests
new workers start
zero dropped connections"] end subgraph Gunicorn["Gunicorn Master"] Master["Master Process
manages workers
signal handling
--max-requests 1000
--max-requests-jitter 50"] Workers["Uvicorn Workers
worker_class UvicornWorker
async event loop
2-4 workers per core
each own memory space"] Config["Gunicorn Config
--timeout 120 (300 AI)
--graceful-timeout 30
--bind unix:/run/gunicorn.sock
--access-logfile -"] end subgraph FastAPI["FastAPI Application"] Endpoints["API Endpoints
async def routes
httpx.AsyncClient
asyncio.Semaphore
connection pooling"] Health["Health Checks
GET /health: liveness
GET /ready: readiness
check DB, Redis, Ollama
200 OK or 503"] end subgraph Backend["Backend Services"] Ollama["Ollama LLM
localhost:11434"] Redis["Redis
cache, rate limit"] Postgres["PostgreSQL
database"] end Client --> SSL SSL --> Proxy SSL --> WS SSL --> Static SSL --> RateLimit Proxy --> Master WS --> Master Service --> Master Security --> Service Reload --> Master Master --> Workers Config --> Master Workers --> Endpoints Endpoints --> Health Endpoints --> Ollama Endpoints --> Redis Endpoints --> Postgres style Nginx fill:#4169E1,color:#fff style Gunicorn fill:#39FF14,color:#000 style Systemd fill:#2D1B69,color:#fff style FastAPI fill:#FF6B6B,color:#fff
Deployment Method Comparison
| Method | Best For | Complexity | Scalability |
|---|---|---|---|
| Uvicorn standalone | Development | Low | Limited |
| Gunicorn + Uvicorn | Single server | Medium | Good |
| Docker + Gunicorn | Container orchestration | Medium | Excellent |
| Kubernetes | Large scale | High | Excellent |
| systemd + Gunicorn | VPS (no Docker) | Low-Medium | Good |
Implementation
from dataclasses import dataclass
from typing import Optional
from enum import Enum
class DeployComponent(Enum):
GUNICORN = "gunicorn"
SYSTEMD = "systemd"
NGINX = "nginx"
HEALTH = "health"
@dataclass
class FastAPIProductionGuide:
"""FastAPI uvicorn nginx production guide."""
def get_gunicorn_config(self) -> str:
"""Gunicorn configuration file."""
return (
"# === GUNICORN CONFIG ===\n"
"# gunicorn.conf.py\n"
"\n"
"import multiprocessing\n"
"import os\n"
"\n"
"# Worker class for ASGI\n"
"worker_class = (\n"
" 'uvicorn.workers.UvicornWorker')\n"
"\n"
"# Workers: I/O-bound AI APIs\n"
"# can run more than CPU count\n"
"# Start with 2, scale with RAM\n"
"# 1 worker per 512MB-1GB\n"
"workers = int(\n"
" os.environ.get(\n"
" 'GUNICORN_WORKERS', 2))\n"
"\n"
"# Bind to Unix socket\n"
"# (not TCP) for Nginx\n"
"bind = (\n"
" 'unix:/run/gunicorn.sock')\n"
"\n"
"# Timeout: 120s standard\n"
"# 300s for AI backends\n"
"timeout = int(\n"
" os.environ.get(\n"
" 'GUNICORN_TIMEOUT',\n"
" 300))\n"
"\n"
"# Graceful shutdown\n"
"graceful_timeout = 30\n"
"\n"
"# Memory leak safety net\n"
"max_requests = 1000\n"
"max_requests_jitter = 50\n"
"\n"
"# Logging\n"
"accesslog = '-'\n"
"errorlog = '-'\n"
"loglevel = 'info'\n"
"\n"
# Performance\n"
"preload_app = True\n"
"\n"
"# Keepalive\n"
"keepalive = 5\n"
"\n"
"# UVloop + httptools\n"
"# (installed separately)\n"
"# UvicornWorker uses them\n"
"# automatically if available"
)
def get_systemd_service(self) -> str:
"""Systemd service unit file."""
return (
"# === SYSTEMD SERVICE ===\n"
"# /etc/systemd/system/\n"
"# fastapi.service\n"
"\n"
"[Unit]\n"
"Description=FastAPI AI API\n"
"After=network.target\n"
"After=redis.service\n"
"After=postgresql.service\n"
"\n"
"[Service]\n"
"Type=notify\n"
"User=appuser\n"
"Group=appuser\n"
"WorkingDirectory=/opt/api\n"
"\n"
"# Environment file\n"
"# (no secrets in unit)\n"
"EnvironmentFile=/opt/api/.env\n"
"\n"
"# Start Gunicorn\n"
"ExecStart=/opt/api/.venv/bin/\n"
" gunicorn main:app \\\n"
" --config gunicorn.conf.py\n"
"\n"
"# Graceful reload\n"
"ExecReload=/bin/kill -s HUP \\\n"
" $MAINPID\n"
"\n"
"# Auto-restart on crash\n"
"Restart=always\n"
"RestartSec=3\n"
"\n"
"# Security hardening\n"
"PrivateTmp=true\n"
"ProtectSystem=strict\n"
"ReadWritePaths=/run /var/log\n"
"NoNewPrivileges=true\n"
"\n"
"# Resource limits\n"
"LimitNOFILE=65536\n"
"\n"
"[Install]\n"
"WantedBy=multi-user.target\n"
"\n"
"# Commands:\n"
"# sudo systemctl daemon-reload\n"
"# sudo systemctl enable --now\n"
"# fastapi\n"
"# sudo systemctl reload fastapi\n"
"# journalctl -u fastapi -f"
)
def get_nginx_config(self) -> str:
"""Nginx reverse proxy configuration."""
return (
"# === NGINX CONFIG ===\n"
"# /etc/nginx/sites-available/api\n"
"\n"
"upstream fastapi_backend {\n"
" server unix:/run/gunicorn.sock\n"
" fail_timeout=0;\n"
"}\n"
"\n"
"server {\n"
" listen 80;\n"
" server_name api.example.com;\n"
" return 301 https://$host\n"
" $request_uri;\n"
"}\n"
"\n"
"server {\n"
" listen 443 ssl http2;\n"
" server_name api.example.com;\n"
" \n"
" # SSL certificates\n"
" ssl_certificate /etc/letsencrypt/\n"
" live/api.example.com/\n"
" fullchain.pem;\n"
" ssl_certificate_key\n"
" /etc/letsencrypt/live/\n"
" api.example.com/privkey.pem;\n"
" \n"
" # SSL settings\n"
" ssl_protocols TLSv1.2 TLSv1.3;\n"
" ssl_ciphers HIGH:!aNULL:\n"
" !MD5;\n"
" ssl_prefer_server_ciphers on;\n"
" \n"
" # Proxy to Gunicorn\n"
" location / {\n"
" proxy_pass\n"
" http://fastapi_backend;\n"
" \n"
" # Headers\n"
" proxy_set_header Host\n"
" $host;\n"
" proxy_set_header X-Real-IP\n"
" $remote_addr;\n"
" proxy_set_header\n"
" X-Forwarded-For\n"
" $proxy_add_x\n"
" _forwarded_for;\n"
" proxy_set_header\n"
" X-Forwarded-Proto\n"
" $scheme;\n"
" \n"
" # Buffering (default on)\n"
" proxy_buffering on;\n"
" \n"
" # Timeouts\n"
" proxy_connect_timeout 60s;\n"
" proxy_read_timeout 300s;\n"
" proxy_send_timeout 60s;\n"
" }\n"
" \n"
" # WebSocket support\n"
" location /ws/ {\n"
" proxy_pass\n"
" http://fastapi_backend;\n"
" proxy_http_version 1.1;\n"
" proxy_set_header Upgrade\n"
" $http_upgrade;\n"
" proxy_set_header Connection\n"
" 'upgrade';\n"
" proxy_read_timeout 300s;\n"
" proxy_buffering off;\n"
" }\n"
" \n"
" # SSE streaming\n"
" location /v1/stream/ {\n"
" proxy_pass\n"
" http://fastapi_backend;\n"
" proxy_buffering off;\n"
" proxy_cache off;\n"
" proxy_read_timeout 300s;\n"
" chunked_transfer_encoding on;\n"
" }\n"
" \n"
" # Rate limiting\n"
" limit_req_zone $binary_remote_addr\n"
" zone=api:10m rate=10r/s;\n"
" \n"
" # Static files (if any)\n"
" location /static/ {\n"
" alias /opt/api/static/;\n"
" expires 30d;\n"
" add_header Cache-Control\n"
" 'public, immutable';\n"
" }\n"
" \n"
" # Health check (no rate limit)\n"
" location /health {\n"
" proxy_pass\n"
" http://fastapi_backend;\n"
" access_log off;\n"
" }\n"
"}"
)
def get_health_endpoints(self) -> str:
"""Health check endpoints."""
return (
"# === HEALTH CHECKS ===\n"
"from fastapi import FastAPI\n"
"import httpx, redis.asyncio as redis\n"
"\n"
"app = FastAPI()\n"
"\n"
"@app.get('/health')\n"
"async def health():\n"
" '''Liveness check.''' \n"
" # Simple: process is alive\n"
" return {'status': 'ok'}\n"
"\n"
"@app.get('/ready')\n"
"async def ready():\n"
" '''Readiness check.''' \n"
" # Check all dependencies\n"
" checks = {}\n"
" \n"
" # Check Ollama\n"
" try:\n"
" async with httpx.AsyncClient(\n"
" timeout=5.0\n"
" ) as client:\n"
" resp = await client.get(\n"
" 'http://localhost'\n"
" ':11434/api/tags')\n"
" checks['ollama'] = (\n"
" resp.status_code == 200)\n"
" except Exception:\n"
" checks['ollama'] = False\n"
" \n"
" # Check Redis\n"
" try:\n"
" r = redis.from_url(\n"
" 'redis://localhost'\n"
" ':6379')\n"
" await r.ping()\n"
" checks['redis'] = True\n"
" await r.aclose()\n"
" except Exception:\n"
" checks['redis'] = False\n"
" \n"
" # Check PostgreSQL\n"
" try:\n"
" # asyncpg or SQLAlchemy\n"
" checks['postgres'] = (\n"
" await check_db())\n"
" except Exception:\n"
" checks['postgres'] = False\n"
" \n"
" all_ok = all(\n"
" checks.values())\n"
" return {\n"
" 'status': 'ok' if all_ok\n"
" else 'degraded',\n"
" 'checks': checks\n"
" } if all_ok else (\n"
" JSONResponse(\n"
" status_code=503,\n"
" content={\n"
" 'status':'degraded',\n"
" 'checks': checks}))"
)
def get_docker_deploy(self) -> str:
"""Docker deployment with Gunicorn."""
return (
"# === DOCKER DEPLOYMENT ===\n"
"# Dockerfile\n"
"\n"
"FROM python:3.12-slim\n"
"\n"
"WORKDIR /app\n"
"\n"
"# Install deps\n"
"COPY requirements.txt .\n"
"RUN pip install --no-cache-dir\n"
" -r requirements.txt\n"
"\n"
"# Copy app\n"
"COPY . .\n"
"\n"
"# Non-root user\n"
"RUN useradd -m appuser\n"
"USER appuser\n"
"\n"
"# Expose port\n"
"EXPOSE 8000\n"
"\n"
"# Health check\n"
"HEALTHCHECK --interval=30s\n"
" --timeout=5s --retries=3\n"
" CMD curl -f http://localhost\n"
" :8000/health || exit 1\n"
"\n"
"# Start Gunicorn\n"
"CMD ['gunicorn', 'main:app',\n"
" '-k',\n"
" 'uvicorn.workers.UvicornWorker',\n"
" '--workers', '4',\n"
" '--bind', '0.0.0.0:8000',\n"
" '--timeout', '300',\n"
" '--graceful-timeout', '30',\n"
" '--max-requests', '1000',\n"
" '--max-requests-jitter', '50']\n"
"\n"
"# docker-compose.yml\n"
"# services:\n"
"# api:\n"
"# build: .\n"
"# ports: ['8000:8000']\n"
"# env_file: .env\n"
"# depends_on:\n"
"# - redis\n"
"# - postgres\n"
"# restart: always\n"
"# deploy:\n"
"# resources:\n"
"# limits:\n"
"# memory: 2G\n"
"# cpus: '2'\n"
"# nginx:\n"
"# image: nginx:alpine\n"
"# ports: ['80:80','443:443']\n"
"# volumes:\n"
"# - ./nginx.conf:/etc/\n"
"# nginx/nginx.conf\n"
"# - ./certs:/etc/nginx/certs\n"
"# depends_on: [api]"
)
def get_production_tips(self) -> dict:
"""Production tips."""
return {
"gunicorn_uvicornworker": "Gunicorn as process manager + UvicornWorker for ASGI. Multi-worker, graceful restart, signal handling.",
"worker_sizing": "Start 2 workers. I/O-bound: cpu*2+1. CPU-bound: cpu_count. 1 worker per 512MB-1GB RAM. 4 on 1GB = OOM.",
"unix_socket": "Unix socket for Nginx-Gunicorn communication — eliminates TCP overhead, simplifies firewall.",
"max_requests": "--max-requests 1000 + jitter = memory leak safety net. Workers gracefully restart after N requests.",
"timeout": "--timeout 120 standard, 300 for AI backends. Kills stuck workers. Set higher than slowest endpoint.",
"graceful": "--graceful-timeout 30. Workers get 30s to finish. HUP signal = graceful reload, zero dropped connections.",
"systemd": "systemd for auto-restart, journald logs, dependency ordering, security hardening (PrivateTmp, ProtectSystem).",
"nginx_ssl": "Nginx SSL termination with Let's Encrypt. TLS 1.2/1.3. proxy_buffering on. proxy_read_timeout 300s for AI.",
"websocket_nginx": "proxy_http_version 1.1, Upgrade header, Connection upgrade, proxy_read_timeout 300s, proxy_buffering off.",
"health_checks": "GET /health (liveness: 200 OK), GET /ready (readiness: check DB, Redis, Ollama). Docker HEALTHCHECK.",
}
FastAPI Uvicorn Nginx Production Checklist
- [ ] Gunicorn as process manager with UvicornWorker for ASGI async support
- [ ] gunicorn main:app -k uvicorn.workers.UvicornWorker
- [ ] Never run uvicorn alone in production — no multi-worker support, weaker signal handling
- [ ] Worker sizing: start with 2 workers, scale with RAM
- [ ] I/O-bound AI APIs (LLM calls): cpu_count() * 2 + 1 workers
- [ ] CPU-bound (embeddings, pandas): cpu_count() workers
- [ ] 1 worker per 512MB to 1GB RAM — 4 workers on 1GB often causes OOM kills
- [ ] Monitor CPU and memory to find optimal worker count
- [ ] --timeout 120 for standard, 300 for AI backends — set higher than slowest legitimate endpoint
- [ ] --graceful-timeout 30 — workers get 30 seconds to finish current requests
- [ ] --max-requests 1000 + --max-requests-jitter 50 — memory leak safety net, not performance knob
- [ ] max_requests with jitter avoids thundering herd of simultaneous worker restarts
- [ ] If API needs more than 120 seconds, that request should be a background job
- [ ] Bind to Unix socket (unix:/run/gunicorn.sock) — eliminates TCP overhead, simplifies firewall
- [ ] Bind to 127.0.0.1 only — Nginx handles public-facing port
- [ ] preload_app = True for faster worker startup (shared imports)
- [ ] keepalive = 5 for connection reuse
- [ ] Install uvloop and httptools for performance — UvicornWorker uses them automatically
- [ ] Write FastAPI app with async/await throughout — blocking calls negate async benefits
- [ ] Use httpx.AsyncClient for HTTP, asyncpg for PostgreSQL — never requests or sync DB drivers
- [ ] systemd service for process management — not tmux, not nohup
- [ ] systemd: ExecStart gunicorn, Restart=always, RestartSec=3
- [ ] systemd: EnvironmentFile for .env — no secrets in unit file
- [ ] systemd: PrivateTmp=true, ProtectSystem=strict — security hardening at zero cost
- [ ] systemd: NoNewPrivileges=true, LimitNOFILE=65536
- [ ] systemd: User=appuser — never run as root
- [ ] systemctl enable --now fastapi — start on boot
- [ ] journalctl -u fastapi -f — view logs
- [ ] systemctl reload fastapi — graceful HUP reload, zero dropped connections
- [ ] Nginx reverse proxy: SSL termination, static files, request buffering, WebSocket
- [ ] Nginx: proxy_pass to Unix socket — fail_timeout=0 for active health checks
- [ ] Nginx: proxy_set_header Host, X-Real-IP, X-Forwarded-For, X-Forwarded-Proto
- [ ] Nginx: proxy_buffering on (default) — frees Gunicorn workers immediately
- [ ] Nginx: proxy_read_timeout 120s standard, 300s for AI backends (default 60s too short)
- [ ] Nginx: WebSocket support — proxy_http_version 1.1, Upgrade header, Connection upgrade
- [ ] Nginx: SSE streaming — proxy_buffering off, proxy_cache off, chunked_transfer_encoding on
- [ ] Nginx: SSL with Let's Encrypt Certbot — TLS 1.2/1.3, ssl_prefer_server_ciphers on
- [ ] Nginx: rate limiting — limit_req_zone per IP
- [ ] Nginx: static files served directly — offload from Gunicorn workers
- [ ] Nginx: HTTP 80 redirect to HTTPS 443
- [ ] Health checks: GET /health (liveness — simple 200 OK)
- [ ] Health checks: GET /ready (readiness — check DB, Redis, Ollama connections)
- [ ] Health checks: return 503 if dependencies down
- [ ] Docker HEALTHCHECK for container orchestration
- [ ] Docker: multi-stage build, non-root user, resource limits
- [ ] Docker: CMD with gunicorn + UvicornWorker
- [ ] Docker: restart: always, depends_on for services
- [ ] Debug mode disabled (DEBUG=false)
- [ ] Secret key set and not in version control
- [ ] Database connections use connection pooling
- [ ] Logging configured for production (JSON format, appropriate level)
- [ ] CORS configured for frontend domains only
- [ ] Rate limiting in place for public endpoints
- [ ] SSL/TLS enabled with valid certificates
- [ ] Environment variables set (not hardcoded)
- [ ] Container runs as non-root user
- [ ] Resource limits set (memory, CPU)
- [ ] Read FastAPI tutorial for API setup
- [ ] Read Docker deployment for Docker setup
- [ ] Read async backend for async patterns
- [ ] Read monitoring for observability
- [ ] Test: Gunicorn starts with correct worker count
- [ ] Test: Nginx proxies requests to Gunicorn socket
- [ ] Test: SSL certificate valid and HTTPS works
- [ ] Test: WebSocket upgrade works through Nginx
- [ ] Test: SSE streaming works through Nginx (no buffering)
- [ ] Test: systemd restarts service on crash
- [ ] Test: systemctl reload gracefully restarts workers
- [ ] Test: health check returns 200, readiness checks dependencies
- [ ] Test: max-requests triggers worker restart without dropped connections
- [ ] Test: timeout kills stuck workers
- [ ] Document worker sizing, timeout config, Nginx config, systemd unit, health check strategy
FAQ
How do you deploy FastAPI with Gunicorn and Uvicorn in production?
Use Gunicorn as process manager with UvicornWorker for ASGI async support. Reflex: "Uvicorn is ASGI server. Gunicorn is process manager that spawns and supervises multiple Uvicorn workers. In production, you almost always want both. gunicorn main:app --worker-class uvicorn.workers.UvicornWorker --workers 4 --bind 0.0.0.0:8000 --timeout 120 --graceful-timeout 30 --max-requests 1000 --max-requests-jitter 50." QubitLogic: "Running uvicorn main:app --reload is development. Production means Gunicorn managing Uvicorn workers, systemd restarting on crash, Nginx terminating TLS. Running uvicorn directly in production — no multi-worker support, weaker signal handling. Use Gunicorn + UvicornWorker." VengalaVinay: "Gunicorn acts as master process, spawning and managing Uvicorn worker processes. Better process management, signal handling, stability under load." Deployment: (1) gunicorn main:app -k uvicorn.workers.UvicornWorker. (2) workers = 2-4 per core. (3) --timeout 120 for slow endpoints. (4) --max-requests 1000 for memory leak safety. (5) --graceful-timeout 30 for clean shutdown. (6) Bind to 127.0.0.1, Nginx handles public.
How many Gunicorn workers should you run for FastAPI AI APIs?
Start with 2 workers, scale with RAM. I/O-bound AI APIs can run more workers than CPU-bound. QubitLogic: "Start with 2 workers. Each Uvicorn worker loads Python app into memory — 4 workers on 1 GB RAM often causes OOM kills. Scale workers with RAM: roughly one worker per 512 MB to 1 GB for lightweight APIs. For I/O-bound routes (LLM API calls), workers spend time waiting — can run more workers than CPU formula suggests. For CPU-bound (embeddings, pandas), stick to 1 worker per vCPU." VengalaVinay: "workers = cpu_count() * 2 + 1 for I/O-bound. For CPU-bound, reduce to cpu_count(). Monitor CPU and memory to find optimal." Sizing: (1) Start: 2 workers. (2) I/O-bound (LLM calls): cpu_count() * 2 + 1. (3) CPU-bound (embeddings): cpu_count(). (4) RAM: 1 worker per 512MB-1GB. (5) 4 workers on 1GB = OOM risk. (6) Monitor and adjust.
How do you configure Nginx as a reverse proxy for FastAPI?
Nginx handles SSL termination, static files, request buffering, and WebSocket upgrade. Reflex: "Nginx handles SSL termination, static file serving, request buffering, connection management — all things Gunicorn should not waste worker time on. proxy_buffering on is default and right choice — Nginx buffers upstream response so Gunicorn workers freed immediately. Unix socket instead of TCP for local communication — eliminates TCP overhead." Vultr: "Nginx reverse proxy: proxy_pass to gunicorn.sock, proxy_set_header Host, X-Real-IP, X-Forwarded-For, X-Forwarded-Proto. WebSocket: proxy_http_version 1.1, Upgrade header, Connection upgrade. Timeout: proxy_read_timeout 120s." QubitLogic: "proxy_read_timeout 120s prevents 504 on slow routes (LLM calls). Default 60s too short for AI backends. For heavy inference, 300s+." Nginx: (1) proxy_pass http://unix:/run/gunicorn.sock. (2) SSL termination with Certbot. (3) proxy_set_header: Host, X-Real-IP, X-Forwarded-For, X-Forwarded-Proto. (4) WebSocket: proxy_http_version 1.1, Upgrade, Connection upgrade. (5) proxy_read_timeout 120s (300s for AI). (6) proxy_buffering on.
How do you set up systemd for FastAPI production deployment?
Use systemd for automatic restarts, log management, dependency ordering, and resource limits. Reflex: "Do not run Gunicorn in tmux. systemd gives automatic restarts, log management via journald, dependency ordering, resource limits. ExecStart gunicorn with UvicornWorker, bind to unix socket. PrivateTmp and ProtectSystem=strict are systemd security hardening. EnvironmentFile loads .env without embedding secrets. ExecReload sends HUP to master — gracefully restarts workers, no dropped connections." OneUptime: "systemd service for non-Docker deployments. ExecStart, Restart=always, User=appuser, EnvironmentFile." systemd: (1) [Unit] Description, After=network.target. (2) [Service] User=appuser, WorkingDirectory=/app. (3) ExecStart=gunicorn main:app -k uvicorn.workers.UvicornWorker --workers 4 --bind unix:/run/gunicorn.sock. (4) Restart=always, RestartSec=3. (5) EnvironmentFile=/app/.env. (6) PrivateTmp=true, ProtectSystem=strict. (7) [Install] WantedBy=multi-user.target. (8) systemctl enable --now fastapi. (9) journalctl for logs. (10) systemctl reload for graceful restart.
How do you handle health checks and graceful restarts in FastAPI production?
Implement liveness and readiness endpoints, use Gunicorn graceful timeout, and reload via HUP signal. Reflex: "reload sends HUP to Gunicorn master — gracefully restarts workers. Existing requests complete, new workers start with updated code. No dropped connections. --graceful-timeout 30 gives workers 30 seconds to finish." OneUptime: "Health checks for load balancers and container orchestration. Include both liveness check and readiness check. Liveness: GET /health returns 200. Readiness: GET /ready checks DB, Redis, Ollama connections." Health checks: (1) GET /health: simple 200 OK (liveness). (2) GET /ready: check DB, Redis, Ollama (readiness). (3) Gunicorn --graceful-timeout 30 for clean shutdown. (4) systemctl reload sends HUP — graceful worker restart. (5) --max-requests 1000 + jitter for memory leak safety. (6) --timeout 120 (300 for AI) kills stuck workers. (7) Nginx fail_timeout=0 for active health checks. (8) Docker HEALTHCHECK for container orchestration.
Want a self-hosted AI company brain that does all of this out of the box?
Book a demo →