uvicorn
ASGI web server for Python: the process that accepts sockets and speaks HTTP/1.1 and WebSockets on behalf of async frameworks like FastAPI, Starlette, and Django in ASGI mode. Installs as pure Python by default, while the [standard] extra swaps in uvloop and httptools for real speed and adds watchfiles for --reload. It is the de facto default server in every FastAPI tutorial and deployment guide.
The default ASGI server for good reason: fast, simple, and everywhere. Accept the HTTP/1.1-only limit, install the [standard] extra, and pair it with a proxy or process manager for production.
Use it if
- You run FastAPI, Starlette, Litestar, or any ASGI app and want the community-default server
- You need WebSocket support alongside plain HTTP in one process
- You want a fast dev loop: --reload restarts the server on file changes via watchfiles
- You deploy containerized services where 'uvicorn app:app --workers 4' behind a proxy is the whole story
- You need HTTP/2: uvicorn speaks only HTTP/1.1 and WebSockets; hypercorn, daphne, or granian cover HTTP/2
- Your app is WSGI (classic Django or Flask): use gunicorn, uvicorn is for ASGI apps
- You want built-in process supervision beyond a flat --workers count: there are no rolling restarts, so serious deployments front it with gunicorn or run one process per container
- You are strict about pre-1.0 dependencies: it is still 0.x and options have been moved or deprecated between releases (the gunicorn worker class left core, for example)
Setup reality
pip install 'uvicorn[standard]' is the install you actually want: the bare package silently skips uvloop, httptools, websockets, and the --reload machinery, and people burn time wondering why reload does nothing. Configuration is CLI flags or uvicorn.run() kwargs, no config file. Behind a reverse proxy you must pass --proxy-headers and set --forwarded-allow-ips or client IPs and https detection come out wrong. The gunicorn worker class moved out of core into the separate uvicorn-worker package, which breaks older deployment recipes.
Patterns
Run a dev server with auto-reloadrun-dev-reload
uvicorn main:app --reload --port 8000--reload requires watchfiles, which only ships with the [standard] extra; on the bare install the flag falls back to a slower stat-based watcher or fails.
Start the server from Pythonrun-programmatic
import uvicorn
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)Pass the app as an import string (not the object) or reload and workers cannot re-import it in child processes.
Run multiple worker processesmultiple-workers
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4--workers is incompatible with --reload; workers share the listening socket but there is no rolling restart of a running fleet.
Run under gunicorn for process managementgunicorn-process-manager
pip install gunicorn uvicorn-worker
gunicorn main:app -w 4 -k uvicorn_worker.UvicornWorker --bind 0.0.0.0:8000The old uvicorn.workers.UvicornWorker class was deprecated out of uvicorn core; the class now lives in the separate uvicorn-worker package.
Trust proxy headers behind nginx or a load balancerbehind-reverse-proxy
uvicorn main:app --host 127.0.0.1 --port 8000 \
--proxy-headers --forwarded-allow-ips='10.0.0.0/8'Without --proxy-headers your app sees the proxy's IP and http scheme; forwarded-allow-ips must include the proxy address or the headers are ignored.
Serve TLS directlyhttps-tls
uvicorn main:app --host 0.0.0.0 --port 443 \
--ssl-keyfile ./key.pem --ssl-certfile ./cert.pemFine for internal services; for public traffic most teams terminate TLS at a proxy and keep uvicorn on plain HTTP.
Listen on a Unix domain socketunix-socket
uvicorn main:app --uds /tmp/uvicorn.sock
# nginx side:
# proxy_pass http://unix:/tmp/uvicorn.sock;Sockets avoid localhost port juggling with a same-host nginx, but check file permissions so the proxy user can connect.
Customize logginglogging-config
uvicorn main:app --log-level warning --no-access-log
# or full control via a dictConfig file:
uvicorn main:app --log-config logging.yamlYAML log configs require PyYAML, which is included in the [standard] extra; JSON and .ini configs work on the bare install.
Load settings from an env fileenv-file
uvicorn main:app --env-file .env--env-file needs python-dotenv (part of the [standard] extra) and only sets process env vars; your app still reads them itself.
Serve a raw ASGI app with no frameworkminimal-asgi-app
# example.py
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, world!'})
# run: uvicorn example:appUseful for understanding what FastAPI abstracts away; headers and bodies are bytes, not strings.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| hypercorn | PyPI | You need HTTP/2 or want to run on trio instead of asyncio |
| daphne | PyPI | Django Channels deployments, where it is the original ASGI server |
| granian | PyPI | A Rust-based server with HTTP/2 when raw throughput is the priority |
| gunicorn | PyPI | WSGI apps, or as the process manager in front of uvicorn workers |