V1
Back to handbooks index
v0.32+ ASGI Python 3.8+
ASGI Server for Python

Uvicorn
Handbook

A complete reference for running async Python web applications — covering ASGI fundamentals, CLI options, production deployment with Gunicorn, Docker, Nginx, and performance tuning.

ASGI / asyncio FastAPI & Starlette HTTP/1.1 + HTTP/2 WebSockets Gunicorn workers Production ready
📡

What is Uvicorn?

A lightning-fast ASGI server built on uvloop and httptools

Uvicorn is a production-grade, minimal ASGI (Asynchronous Server Gateway Interface) server for Python. It acts as the bridge between your async Python web application and the outside world — it listens for HTTP/WebSocket connections and passes them to your app following the ASGI spec.

It is built on two C-extension libraries: uvloop (a fast asyncio event loop based on libuv) and httptools (a fast HTTP parser based on Node.js's http-parser). This gives it throughput comparable to Go and Node.js.

ASGI Server

Uvicorn implements the ASGI spec — it runs your async Python app the same way Apache runs PHP. It is the runtime layer, not the framework.

Built for async

Designed from the ground up for Python's asyncio. Natively supports async def views, WebSockets, Server-Sent Events, and long-lived connections.

Framework agnostic

Works with any ASGI-compatible framework: FastAPI, Starlette, Django (≥3.0), Quart, Litestar, BlackSheep, and more.

How Uvicorn fits in the stack

🌍ClientBrowser / API
🌐Reverse proxyNginx / Caddy
🦄Process mgrGunicorn
ASGI serverUvicorn worker
🧩FrameworkFastAPI / Starlette
📦App logicYour code
🆕
Uvicorn v0.30+ introduces lifespan protocol improvements, HTTP/2 support via h2, and first-class support for uvicorn[standard] extras which bundles uvloop, httptools, and websockets for maximum performance.
💡

Why Use Uvicorn?

Performance, async support, and the ASGI ecosystem
Exceptional throughput

uvloop replaces asyncio's default event loop with a Cython-based implementation of libuv, delivering 2–4× faster I/O performance compared to the stdlib event loop.

True async concurrency

Unlike WSGI servers (Gunicorn, uWSGI) that rely on threads or processes per request, Uvicorn can handle thousands of concurrent connections on a single thread via coroutines.

WebSocket & SSE support

First-class, spec-compliant support for WebSocket connections and Server-Sent Events — no bolt-on solutions needed.

Hot reload for dev

--reload watches your source files and restarts the server on changes. Supports watchfiles for faster, more precise detection.

Minimal footprint

Uvicorn itself has very few dependencies. It does one thing well — serve ASGI apps — and composes cleanly with Gunicorn for process management.

Industry standard

The default server for FastAPI (used at Uber, Netflix, Microsoft) and Starlette. Used in production at massive scale worldwide.

Benchmark context: In independent TechEmpower benchmarks, FastAPI + Uvicorn consistently ranks among the top Python web frameworks — outperforming Django + Gunicorn by 3–10× on JSON serialisation and plain-text responses, while matching Node.js Express in many scenarios.
🕐

When to Use Uvicorn

Right tool for the right job
✅ Use Uvicorn when…
  • You're building with FastAPI, Starlette, or any ASGI framework
  • Your app is I/O-heavy (DB queries, HTTP calls, file reads)
  • You need WebSocket or SSE support
  • You want high concurrency without scaling to many processes
  • You're doing async background tasks in your web process
  • You're building APIs, streaming endpoints, or chat-like systems
❌ Consider alternatives when…
  • Your codebase is entirely synchronous Django/Flask (use Gunicorn + gevent)
  • You need battle-hardened WSGI compatibility (uWSGI)
  • You have CPU-heavy workloads where more processes matter more than async
  • You're on a platform where uvloop isn't available (pure Python fallback is slower)
  • You need Mod_WSGI Apache integration (ASGI isn't supported there)
ℹ️
WSGI vs ASGI: Django 3.0+ and Flask 2.0+ both support ASGI, so you can use Uvicorn with them too. However, if your views are synchronous, the performance benefit is minimal — the async layer adds overhead for purely sync code.
📦

Installation

pip, extras, and optional high-performance dependencies
bash
# Minimal install (pure Python fallback, no uvloop/httptools) pip install uvicorn # RECOMMENDED: full performance extras pip install "uvicorn[standard]" # Installs: uvicorn + uvloop + httptools + websockets + watchfiles # With Gunicorn for production process management pip install "uvicorn[standard]" gunicorn # Verify install uvicorn --version # → Running uvicorn 0.32.0 with CPython 3.12.0 on Linux

What each extra installs

PackagePurposeIncluded in [standard]
uvloopFast asyncio event loop (C extension, 2–4× faster). Falls back to asyncio on Windows.
httptoolsFast HTTP/1.1 parser (C extension, from Node.js http-parser)
websocketsWebSocket protocol implementation
watchfilesEfficient file watching for --reload (replaces older watchdog)
python-dotenvLoad .env files for environment configuration❌ (manual)
h2HTTP/2 protocol support❌ (manual)
⚠️
Windows note: uvloop does not support Windows. On Windows, Uvicorn falls back to the standard asyncio event loop automatically. Performance is still acceptable but lower than Linux/macOS. For production, deploy on Linux.

Quick Start

Your first ASGI app running in 60 seconds

Minimal ASGI app (no framework)

python — main.py
# A raw ASGI app — no framework needed async def app(scope, receive, send): assert scope['type'] == 'http' await send({ 'type': 'http.response.start', 'status': 200, 'headers': [[b'content-type', b'text/plain']], }) await send({ 'type': 'http.response.body', 'body': b'Hello, Uvicorn!', })
bash
uvicorn main:app # → INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

With FastAPI (typical usage)

python — main.py
from fastapi import FastAPI app = FastAPI() @app.get("/") async def root(): return {"message": "Hello World"} @app.get("/items/{item_id}") async def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q}
bash
# Development — with auto-reload uvicorn main:app --reload # Custom host and port uvicorn main:app --host 0.0.0.0 --port 8080 --reload # Production — multiple workers uvicorn main:app --workers 4 --host 0.0.0.0 --port 8000

Running programmatically

python
import uvicorn if __name__ == "__main__": uvicorn.run( "main:app", # module:attribute string (enables reload) host="0.0.0.0", port=8000, reload=True, # dev only log_level="info", ) # Or pass the app object directly (reload not available) from main import app uvicorn.run(app, host="127.0.0.1", port=8000)
🔌

ASGI Primer

The protocol Uvicorn implements — scope, receive, send

ASGI is the successor to WSGI. Where WSGI is synchronous and request/response only, ASGI is async and supports long-lived connections (WebSockets, SSE, HTTP/2 push). Every ASGI app is a callable with this signature:

python
# Every ASGI app is an async callable(scope, receive, send) # scope → dict describing the connection # receive → awaitable — yields incoming events (request body, WS messages) # send → awaitable — sends events back (response start, body, WS message) async def app(scope, receive, send): if scope['type'] == 'http': # scope keys: method, path, query_string, headers, client, server body_event = await receive() # {'type': 'http.request', 'body': b'...', 'more_body': False} await send({'type': 'http.response.start', 'status': 200, 'headers': [[b'content-type', b'application/json']]}) await send({'type': 'http.response.body', 'body': b'{"ok": true}'}) elif scope['type'] == 'websocket': await send({'type': 'websocket.accept'}) event = await receive() if event['type'] == 'websocket.receive': await send({'type': 'websocket.send', 'text': event['text']}) elif scope['type'] == 'lifespan': while True: event = await receive() if event['type'] == 'lifespan.startup': # initialize DB pool, load models, etc. await send({'type': 'lifespan.startup.complete'}) elif event['type'] == 'lifespan.shutdown': # teardown gracefully await send({'type': 'lifespan.shutdown.complete'}) return
ℹ️
Lifespan events are the ASGI way to run startup/shutdown logic — initialize database connection pools, ML models, background tasks. FastAPI exposes this via @app.on_event("startup") or the newer lifespan context manager parameter.
🧩

Compatible Frameworks

Every ASGI framework works with Uvicorn out of the box
bash — any ASGI framework
# FastAPI pip install fastapi uvicorn[standard] uvicorn main:app --reload # Starlette pip install starlette uvicorn[standard] uvicorn app:app --reload # Django (3.0+ ASGI) pip install django uvicorn[standard] uvicorn myproject.asgi:application --reload # Litestar (formerly Starlite) pip install litestar uvicorn[standard] uvicorn app:app --reload # Quart (async Flask-like) pip install quart uvicorn[standard] uvicorn app:app --reload
FrameworkASGI?Built-in dev serverNotes
FastAPI✅ NativeUses Uvicorn internally (fastapi dev)Most popular combo. FastAPI wraps Starlette.
Starlette✅ NativeUvicorn's closest sibling project (same author)
Django 3.0+✅ via asgi.pyrunserver (Daphne)Point Uvicorn at myproject.asgi:application
Flask 2.0+⚠️ LimitedWerkzeug dev serverAsync views only; sync views have overhead
Litestar✅ NativeUses Uvicorn internallyHigh-performance alternative to FastAPI
Quart✅ NativeHypercornFlask API, async implementation
Sanic❌ CustomBuilt-in serverSanic has its own server; doesn't use ASGI/Uvicorn
💻

CLI Reference

Every command-line flag explained
bash — full syntax
uvicorn [OPTIONS] APP # APP format: module_path:attribute # Examples: uvicorn main:app # main.py, variable `app` uvicorn mypackage.server:create_app # factory function uvicorn app.main:app # nested module

Connection & binding

FlagDefaultDescription
--host TEXT127.0.0.1Bind to this host. Use 0.0.0.0 to listen on all interfaces (production).
--port INT8000Bind to this port.
--uds TEXTBind to a Unix domain socket path (e.g. /tmp/uvicorn.sock). Faster than TCP for local Nginx proxy.
--fd INTBind to a socket from a file descriptor (systemd socket activation).

Workers & concurrency

FlagDefaultDescription
--workers INT1Number of worker processes. Rule of thumb: (2 × CPU cores) + 1. Use Gunicorn instead for better process management.
--loop [auto|asyncio|uvloop]autoEvent loop implementation. auto picks uvloop if available.
--http [auto|h11|httptools]autoHTTP implementation. auto picks httptools if available.
--ws [auto|none|websockets|wsproto]autoWebSocket implementation.
--lifespan [auto|on|off]autoControl ASGI lifespan events. Set on to require startup/shutdown handlers.
--limit-concurrency INTMaximum concurrent connections before returning 503 Service Unavailable.
--backlog INT2048Maximum number of connections to hold in the listen backlog.

Development

FlagDefaultDescription
--reloadFalseEnable auto-reload when source files change. Requires app passed as a string ("main:app"), not an import.
--reload-dir PATHcwdDirectory to watch. Can be specified multiple times. Defaults to current working directory.
--reload-include GLOB*.pyGlob patterns of files to include in watch. Example: *.py *.html *.jinja2
--reload-exclude GLOBGlob patterns to exclude from watch. Example: tests/*
--reload-delay FLOAT0.25Seconds to wait before triggering reload after a file change.

Logging

FlagDefaultDescription
--log-level [critical|error|warning|info|debug|trace]infoLog level for the Uvicorn logger.
--log-config PATHPath to a JSON or YAML logging config file (Python logging dict config format).
--access-log / --no-access-logTrueEnable/disable HTTP access logging. Disable in production if Nginx logs requests instead.
--use-colors / --no-use-colorsautoColorized output. Disabled automatically if not writing to a TTY.

Timeouts & limits

FlagDefaultDescription
--timeout-keep-alive INT5Seconds to wait for a new request on a keep-alive connection before closing it.
--timeout-notify INT30Seconds between SIGSIGNAL to Gunicorn (workers only). Prevents "Worker timed out" false positives.
--timeout-graceful-shutdown INTSeconds to wait for in-flight requests to complete before forcing shutdown on SIGTERM.
--limit-max-requests INTMax requests per worker before restarting. Useful to combat memory leaks over time.
--header NAME VALUEInject a custom response header on every request. E.g. --header X-Served-By uvicorn
⚙️

Configuration File

uvicorn.toml / pyproject.toml / programmatic Config object

Uvicorn does not have a native config file format, but you can use pyproject.toml, or pass config as a Python dict via the programmatic API. Many teams use a gunicorn.conf.py when running with Gunicorn (see the Gunicorn section).

Via pyproject.toml (using Gunicorn)

toml — pyproject.toml
[tool.uvicorn] host = "0.0.0.0" port = 8000 workers = 4 log_level = "info" access_log = true reload = false

Programmatic Config object

python
import uvicorn from uvicorn.config import Config from uvicorn.main import Server from main import app config = Config( app=app, host="0.0.0.0", port=8000, workers=4, log_level="info", access_log=True, reload=False, timeout_keep_alive=30, limit_max_requests=10000, # recycle workers after 10k requests ) server = Server(config) # Run inside an existing event loop import asyncio asyncio.run(server.serve()) # Or use the simple shorthand uvicorn.run(app, host="0.0.0.0", port=8000)
Best practice: Use environment variables for environment-specific config (port, workers, log level). Keep a gunicorn.conf.py file for production settings. Avoid hardcoding host/port in application code.
👷

Workers & Concurrency

Processes, threads, and the async event loop

Uvicorn handles concurrency through the asyncio event loop — a single process can handle thousands of concurrent I/O-bound requests. For CPU-bound scaling, use multiple worker processes.

Single worker (default)

One process, one event loop. All requests are handled concurrently via coroutines. Great for development and for apps on containers with a single vCPU. Handles thousands of simultaneous connections.

Multiple workers

Multiple OS processes, each with its own event loop. Necessary for multi-core servers. Formula: (2 × CPU cores) + 1. State is not shared between workers — use Redis/DB for session state.

bash
# 4 workers for a 2-core machine (formula: 2 × cores + 1) uvicorn main:app --workers 4 # Recycle workers after N requests (prevents memory leaks) uvicorn main:app --workers 4 --limit-max-requests 10000 # Limit max concurrent connections (return 503 if exceeded) uvicorn main:app --limit-concurrency 500
⚠️
Avoid blocking the event loop. All I/O must be async. Calling synchronous blocking functions (e.g. requests.get(), time.sleep(), CPU-heavy code) in an async def view will block the entire event loop and starve all other requests. Use httpx.AsyncClient, asyncio.sleep(), or run sync code in a thread pool via loop.run_in_executor().
python — avoiding event loop blocking
import asyncio from fastapi import FastAPI app = FastAPI() # ❌ WRONG — blocks event loop @app.get("/bad") async def bad_endpoint(): import time time.sleep(2) # blocks ALL concurrent requests for 2 seconds return {"status": "done"} # ✅ CORRECT — yields control back to event loop @app.get("/good") async def good_endpoint(): await asyncio.sleep(2) # other requests run during this pause return {"status": "done"} # ✅ Run sync code without blocking (thread pool) @app.get("/sync-safe") async def sync_safe_endpoint(): loop = asyncio.get_event_loop() result = await loop.run_in_executor(None, some_blocking_function) return {"result": result}
🔒

SSL / TLS

HTTPS directly on Uvicorn (dev) or via Nginx (production)
bash — development self-signed cert
# Generate a self-signed cert for local dev openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes \ -subj "/CN=localhost" # Run Uvicorn with SSL uvicorn main:app --ssl-keyfile key.pem --ssl-certfile cert.pem --port 443
python — programmatic SSL
uvicorn.run( "main:app", host="0.0.0.0", port=443, ssl_keyfile="./key.pem", ssl_certfile="./cert.pem", ssl_ca_certs="./ca-bundle.pem", # optional: client cert verification ssl_ciphers="TLSv1.2", # optional: minimum TLS version )
ℹ️
Production recommendation: Terminate TLS at Nginx or a load balancer, not at Uvicorn. Let Nginx handle certificate management (certbot/Let's Encrypt), HTTP→HTTPS redirects, and HTTP/2. Uvicorn talks plain HTTP on localhost behind the proxy. This is simpler and gives you more control.
🦄

Gunicorn + Uvicorn

The recommended production deployment pattern

For production, use Gunicorn as the process manager with UvicornWorker as the worker class. Gunicorn handles worker lifecycle, graceful restarts, and health monitoring. Uvicorn handles the actual async I/O within each worker process.

bash
pip install gunicorn "uvicorn[standard]" # Basic — 4 workers on port 8000 gunicorn main:app \ --workers 4 \ --worker-class uvicorn.workers.UvicornWorker \ --bind 0.0.0.0:8000 # With Unix socket (recommended with Nginx) gunicorn main:app \ --workers 4 \ --worker-class uvicorn.workers.UvicornWorker \ --bind unix:/run/uvicorn.sock \ --umask 007 \ --daemon \ --pid /run/gunicorn.pid # For HTTP/2 support gunicorn main:app \ --worker-class uvicorn.workers.UvicornH2CWorker \ --workers 4

gunicorn.conf.py — recommended approach

python — gunicorn.conf.py
import multiprocessing import os # Binding bind = f"0.0.0.0:{os.getenv('PORT', '8000')}" backlog = 2048 # Worker processes workers = 2 * multiprocessing.cpu_count() + 1 worker_class = "uvicorn.workers.UvicornWorker" worker_connections = 1000 # Restart workers to prevent memory leaks max_requests = 10000 max_requests_jitter = 1000 # randomise to avoid thundering herd # Timeouts timeout = 120 keepalive = 5 graceful_timeout = 30 # Logging accesslog = "-" # stdout errorlog = "-" # stdout loglevel = "info" access_log_format = '%(h)s "%(r)s" %(s)s %(b)s %(D)sµs' # Process naming proc_name = "myapp-gunicorn"
bash — run with config file
gunicorn main:app --config gunicorn.conf.py
🐳

Docker

Production-ready Dockerfile patterns

Single-stage (simple)

dockerfile
FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]

Multi-stage (production, smaller image)

dockerfile
# Build stage FROM python:3.12-slim AS builder WORKDIR /app COPY requirements.txt . RUN pip install --prefix=/install --no-cache-dir -r requirements.txt # Production stage FROM python:3.12-slim AS production WORKDIR /app # Create non-root user RUN groupadd -r appuser && useradd -r -g appuser appuser # Copy dependencies from builder COPY --from=builder /install /usr/local # Copy application code COPY --chown=appuser:appuser . . USER appuser EXPOSE 8000 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" CMD ["gunicorn", "main:app", \ "--worker-class", "uvicorn.workers.UvicornWorker", \ "--workers", "4", \ "--bind", "0.0.0.0:8000", \ "--timeout", "120", \ "--access-logfile", "-", \ "--error-logfile", "-"]
bash — build and run
docker build -t myapp:latest . docker run -d -p 8000:8000 --name myapp myapp:latest # With environment variables docker run -d -p 8000:8000 \ -e DATABASE_URL=postgresql://... \ -e SECRET_KEY=... \ myapp:latest
🌐

Nginx Reverse Proxy

TLS termination, static files, and WebSocket proxying
nginx — /etc/nginx/sites-available/myapp
upstream uvicorn { server unix:/run/uvicorn.sock fail_timeout=0; # Or TCP: server 127.0.0.1:8000 fail_timeout=0; } server { listen 80; server_name example.com www.example.com; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name example.com www.example.com; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_session_cache shared:SSL:10m; # Security headers add_header X-Content-Type-Options nosniff; add_header X-Frame-Options DENY; add_header X-XSS-Protection "1; mode=block"; add_header Strict-Transport-Security "max-age=31536000" always; # Static files served directly by Nginx location /static/ { alias /app/static/; expires 30d; add_header Cache-Control "public, immutable"; } # WebSocket upgrade headers location /ws/ { proxy_pass http://uvicorn; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_read_timeout 86400; # long-lived WS connections } # All other requests → Uvicorn location / { proxy_pass http://uvicorn; 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_redirect off; proxy_buffering off; # disable for streaming responses / SSE } }
⚠️
Forwarded headers: When behind Nginx, your app sees 127.0.0.1 as the client IP. Use the X-Forwarded-For and X-Forwarded-Proto headers to get the real client IP and scheme. In FastAPI/Starlette, add ProxyHeadersMiddleware to trust these headers. Only enable this if you are behind a trusted proxy.
📋

Logging

Access logs, structured JSON, and custom log config
python — log_config.json
{ "version": 1, "disable_existing_loggers": false, "formatters": { "default": { "format": "%(asctime)s %(levelname)s %(name)s %(message)s", "datefmt": "%Y-%m-%d %H:%M:%S" }, "access": { "format": "%(asctime)s %(levelname)s [%(name)s] %(client_addr)s - \"%(request_line)s\" %(status_code)s" } }, "handlers": { "default": { "class": "logging.StreamHandler", "formatter": "default", "stream": "ext://sys.stderr" }, "access": { "class": "logging.StreamHandler", "formatter": "access", "stream": "ext://sys.stdout" } }, "loggers": { "uvicorn": { "handlers": ["default"], "level": "INFO", "propagate": false }, "uvicorn.error": { "level": "INFO" }, "uvicorn.access": { "handlers": ["access"], "level": "INFO", "propagate": false } } }
bash
uvicorn main:app --log-config log_config.json

Structured JSON logging (with python-json-logger)

python — structured logging setup
pip install python-json-logger # In your app's logging setup: import logging from pythonjsonlogger import jsonlogger logger = logging.getLogger() handler = logging.StreamHandler() formatter = jsonlogger.JsonFormatter( fmt='%(asctime)s %(levelname)s %(name)s %(message)s' ) handler.setFormatter(formatter) logger.addHandler(handler) logger.setLevel(logging.INFO)
⚖️

Server Comparison

Uvicorn vs Gunicorn vs Hypercorn vs Daphne vs uWSGI
Feature Uvicorn Gunicorn Hypercorn Daphne uWSGI
Protocol ASGI WSGI (+ ASGI via worker) ASGI ASGI WSGI
HTTP/2 (via h2) native ~ partial
WebSockets
Process management ~ basic excellent ~ basic ~ basic excellent
Hot reload (dev)
Performance ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐
Production maturity ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐
Best for FastAPI / Starlette / ASGI apps WSGI apps + Uvicorn workers HTTP/2-heavy apps Django Channels Legacy Python apps
Production recommendation: Use Gunicorn + UvicornWorker rather than running Uvicorn directly with --workers. Gunicorn provides superior process management, graceful restarts, and health monitoring that Uvicorn's own multi-process mode lacks.
📚

Programmatic API

Run and control Uvicorn from within your Python code
python
import asyncio import signal import uvicorn from uvicorn.config import Config from uvicorn.main import Server # ── Simple run ── uvicorn.run("main:app", host="0.0.0.0", port=8000) # ── Advanced: run alongside other async tasks ── async def serve(): config = Config("main:app", host="0.0.0.0", port=8000, log_level="info") server = Server(config) await server.serve() # ── Run server + background task concurrently ── async def background_worker(): while True: await asyncio.sleep(60) # do something every minute async def main(): config = Config("main:app", host="0.0.0.0", port=8000) server = Server(config) async with asyncio.TaskGroup() as tg: tg.create_task(server.serve()) tg.create_task(background_worker()) asyncio.run(main()) # ── Graceful shutdown from inside the app ── async def run_with_shutdown(): config = Config("main:app", host="0.0.0.0", port=8000) server = Server(config) loop = asyncio.get_event_loop() def shutdown(): server.should_exit = True # trigger graceful shutdown loop.add_signal_handler(signal.SIGTERM, shutdown) await server.serve()

Key uvicorn.run() parameters

ParameterTypeDefaultDescription
appstr | callableASGI app or module:attribute string
hoststr"127.0.0.1"Bind host
portint8000Bind port
udsstrNoneUnix domain socket path
reloadboolFalseEnable auto-reload (app must be string)
reload_dirslist[str]NoneDirectories to watch for reload
workersintNoneNumber of worker processes
log_levelstr"info"Logging level
access_logboolTrueEnable access logging
use_colorsboolNoneColorised output (auto-detected)
proxy_headersboolTrueTrust X-Forwarded-* headers
forwarded_allow_ipsstr"127.0.0.1"IPs to trust for proxy headers
ssl_keyfilestrNonePath to SSL private key
ssl_certfilestrNonePath to SSL certificate
timeout_keep_aliveint5Keep-alive timeout in seconds
limit_max_requestsintNoneWorker restarts after N requests
limit_concurrencyintNoneMax concurrent connections
🗒️

Cheat Sheet

Most-used commands at a glance
Development
# Auto-reload on file change uvicorn main:app --reload # Custom port uvicorn main:app --reload --port 3000 # Watch extra dirs (e.g. templates) uvicorn main:app --reload \ --reload-dir src --reload-dir templates # Debug log level uvicorn main:app --reload --log-level debug
Production (direct)
# Multi-worker on all interfaces uvicorn main:app --host 0.0.0.0 \ --workers 4 # Unix socket (Nginx-friendly) uvicorn main:app \ --uds /tmp/uvicorn.sock # Limit connections, recycle workers uvicorn main:app --workers 4 \ --limit-max-requests 10000 \ --no-access-log
Gunicorn + Uvicorn
# Basic gunicorn main:app \ -w 4 \ -k uvicorn.workers.UvicornWorker # With config file gunicorn main:app \ -c gunicorn.conf.py # Reload workers gracefully kill -HUP $(cat /run/gunicorn.pid)
Programmatic
import uvicorn # Simplest uvicorn.run("main:app", reload=True) # Production config uvicorn.run( "main:app", host="0.0.0.0", port=8000, workers=4, log_level="info", )
SSL
# Generate self-signed cert openssl req -x509 -newkey rsa:4096 \ -keyout key.pem -out cert.pem \ -days 365 -nodes -subj "/CN=localhost" # Run with SSL uvicorn main:app \ --ssl-keyfile key.pem \ --ssl-certfile cert.pem \ --port 443
Environment vars
# Uvicorn reads these env vars UVICORN_HOST=0.0.0.0 UVICORN_PORT=8000 UVICORN_WORKERS=4 UVICORN_LOG_LEVEL=info UVICORN_RELOAD=false UVICORN_ACCESS_LOG=true # Then just: uvicorn main:app
📚
Further reading: Official docs at www.uvicorn.org. ASGI spec at asgi.readthedocs.io. For FastAPI-specific deployment patterns see fastapi.tiangolo.com/deployment. For benchmarks, check www.techempower.com/benchmarks (Python section).