## File: README.md --- [](https://github.com/Kludex/uvicorn/actions) [](https://pypi.python.org/pypi/uvicorn) [](https://pypi.org/project/uvicorn) [](https://discord.gg/RxKUF5JuHs) --- **Documentation**: [https://uvicorn.dev](https://uvicorn.dev) **Source Code**: [https://www.github.com/Kludex/uvicorn](https://www.github.com/Kludex/uvicorn) --- Uvicorn is an ASGI web server implementation for Python. Until recently Python has lacked a minimal low-level server/application interface for async frameworks. The [ASGI specification][asgi] fills this gap, and means we're now able to start building a common set of tooling usable across all async frameworks. Uvicorn supports HTTP/1.1 and WebSockets. ## Quickstart Install using `pip`: ```shell $ pip install uvicorn ``` This will install uvicorn with minimal (pure Python) dependencies. ```shell $ pip install 'uvicorn[standard]' ``` This will install uvicorn with "Cython-based" dependencies (where possible) and other "optional extras". In this context, "Cython-based" means the following: - the event loop `uvloop` will be installed and used if possible. - the http protocol will be handled by `httptools` if possible. Moreover, "optional extras" means that: - the websocket protocol will be handled by `websockets` (should you want to use `wsproto` you'd need to install it manually) if possible. - the `--reload` flag in development mode will use `watchfiles`. - `python-dotenv` will be installed should you want to use the `--env-file` option. - `PyYAML` will be installed to allow you to provide a `.yaml` file to `--log-config`, if desired. Create an application, in `example.py`: ```python 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 the server: ```shell $ uvicorn example:app ``` --- ## Why ASGI? Most well established Python Web frameworks started out as WSGI-based frameworks. WSGI applications are a single, synchronous callable that takes a request and returns a response. This doesn’t allow for long-lived connections, like you get with long-poll HTTP or WebSocket connections, which WSGI doesn't support well. Having an async concurrency model also allows for options such as lightweight background tasks, and can be less of a limiting factor for endpoints that have long periods being blocked on network I/O such as dealing with slow HTTP requests. --- ## Alternative ASGI servers A strength of the ASGI protocol is that it decouples the server implementation from the application framework. This allows for an ecosystem of interoperating webservers and application frameworks. ### Daphne The first ASGI server implementation, originally developed to power Django Channels, is [the Daphne webserver][daphne]. It is run widely in production, and supports HTTP/1.1, HTTP/2, and WebSockets. Any of the example applications given here can equally well be run using `daphne` instead. ``` $ pip install daphne $ daphne app:App ``` ### Hypercorn [Hypercorn][hypercorn] was initially part of the Quart web framework, before being separated out into a standalone ASGI server. Hypercorn supports HTTP/1.1, HTTP/2, and WebSockets. It also supports [the excellent `trio` async framework][trio], as an alternative to `asyncio`. ``` $ pip install hypercorn $ hypercorn app:App ``` ### Mangum [Mangum][mangum] is an adapter for using ASGI applications with AWS Lambda & API Gateway. ### Granian [Granian][granian] is an ASGI compatible Rust HTTP server which supports HTTP/2, TLS and WebSockets. --- [asgi]: https://asgi.readthedocs.io/en/latest/ [daphne]: https://github.com/django/daphne [hypercorn]: https://github.com/pgjones/hypercorn [trio]: https://trio.readthedocs.io [mangum]: https://github.com/jordaneremieff/mangum [granian]: https://github.com/emmett-framework/granian --- ## File: docs/concepts/asgi.md ## ASGI **Uvicorn** uses the [ASGI specification](https://asgi.readthedocs.io/en/latest/) for interacting with an application. The application should expose an async callable which takes three arguments: * `scope` - A dictionary containing information about the incoming connection. * `receive` - A channel on which to receive incoming messages from the server. * `send` - A channel on which to send outgoing messages to the server. Two common patterns you might use are either function-based applications: ```python async def app(scope, receive, send): assert scope['type'] == 'http' ... ``` Or instance-based applications: ```python class App: async def __call__(self, scope, receive, send): assert scope['type'] == 'http' ... app = App() ``` It's good practice for applications to raise an exception on scope types that they do not handle. The content of the `scope` argument, and the messages expected by `receive` and `send` depend on the protocol being used. The format for HTTP messages is described in the [ASGI HTTP Message format](https://asgi.readthedocs.io/en/latest/specs/www.html). ### HTTP Scope An incoming HTTP request might have a connection `scope` like this: ```python { 'type': 'http', 'scheme': 'http', 'root_path': '', 'server': ('127.0.0.1', 8000), 'http_version': '1.1', 'method': 'GET', 'path': '/', 'headers': [ (b'host', b'127.0.0.1:8000'), (b'user-agent', b'curl/7.51.0'), (b'accept', b'*/*') ] } ``` ### HTTP Messages The instance coroutine communicates back to the server by sending messages to the `send` coroutine. ```python 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!', }) ``` ### Requests & responses Here's an example that displays the method and path used in the incoming request: ```python async def app(scope, receive, send): """ Echo the method and path back in an HTTP response. """ assert scope['type'] == 'http' body = f'Received {scope["method"]} request to {scope["path"]}' await send({ 'type': 'http.response.start', 'status': 200, 'headers': [ [b'content-type', b'text/plain'], ] }) await send({ 'type': 'http.response.body', 'body': body.encode('utf-8'), }) ``` ### Reading the request body You can stream the request body without blocking the asyncio task pool, by fetching messages from the `receive` coroutine. ```python async def read_body(receive): """ Read and return the entire body from an incoming ASGI message. """ body = b'' more_body = True while more_body: message = await receive() body += message.get('body', b'') more_body = message.get('more_body', False) return body async def app(scope, receive, send): """ Echo the request body back in an HTTP response. """ body = await read_body(receive) await send({ 'type': 'http.response.start', 'status': 200, 'headers': [ (b'content-type', b'text/plain'), (b'content-length', str(len(body)).encode()) ] }) await send({ 'type': 'http.response.body', 'body': body, }) ``` ### Streaming responses You can stream responses by sending multiple `http.response.body` messages to the `send` coroutine. ```python import asyncio async def app(scope, receive, send): """ Send a slowly streaming HTTP response back to the client. """ await send({ 'type': 'http.response.start', 'status': 200, 'headers': [ [b'content-type', b'text/plain'], ] }) for chunk in [b'Hello', b', ', b'world!']: await send({ 'type': 'http.response.body', 'body': chunk, 'more_body': True }) await asyncio.sleep(1) await send({ 'type': 'http.response.body', 'body': b'', }) ``` --- ## Why ASGI? Most well established Python Web frameworks started out as WSGI-based frameworks. WSGI applications are a single, synchronous callable that takes a request and returns a response. This doesn’t allow for long-lived connections, like you get with long-poll HTTP or WebSocket connections, which WSGI doesn't support well. Having an async concurrency model also allows for options such as lightweight background tasks, and can be less of a limiting factor for endpoints that have long periods being blocked on network I/O such as dealing with slow HTTP requests. --- ## Alternative ASGI servers A strength of the ASGI protocol is that it decouples the server implementation from the application framework. This allows for an ecosystem of interoperating webservers and application frameworks. ### Daphne The first ASGI server implementation, originally developed to power Django Channels, is [the Daphne webserver](https://github.com/django/daphne). It is run widely in production, and supports HTTP/1.1, HTTP/2, and WebSockets. Any of the example applications given here can equally well be run using `daphne` instead. ```shell pip install daphne daphne app:App ``` ### Hypercorn [Hypercorn](https://github.com/pgjones/hypercorn) was initially part of the Quart web framework, before being separated out into a standalone ASGI server. Hypercorn supports HTTP/1.1, HTTP/2, HTTP/3 and WebSockets. ```shell pip install hypercorn hypercorn app:App ``` --- ## ASGI frameworks You can use Uvicorn, Daphne, or Hypercorn to run any ASGI framework. For small services you can also write ASGI applications directly. ### Starlette [Starlette](https://github.com/Kludex/starlette) is a lightweight ASGI framework/toolkit. It is ideal for building high performance asyncio services, and supports both HTTP and WebSockets. ### Django Channels The ASGI specification was originally designed for use with [Django Channels](https://channels.readthedocs.io/en/latest/). Channels is a little different to other ASGI frameworks in that it provides an asynchronous frontend onto a threaded-framework backend. It allows Django to support WebSockets, background tasks, and long-running connections, with application code still running in a standard threaded context. ### Quart [Quart](https://pgjones.gitlab.io/quart/) is a Flask-like ASGI web framework. ### FastAPI [**FastAPI**](https://github.com/tiangolo/fastapi) is an API framework based on **Starlette** and **Pydantic**, heavily inspired by previous server versions of **APIStar**. You write your API function parameters with Python 3.6+ type declarations and get automatic data conversion, data validation, OpenAPI schemas (with JSON Schemas) and interactive API documentation UIs. ### BlackSheep [BlackSheep](https://www.neoteroi.dev/blacksheep/) is a web framework based on ASGI, inspired by Flask and ASP.NET Core. Its most distinctive features are built-in support for dependency injection, automatic binding of parameters by request handler's type annotations, and automatic generation of OpenAPI documentation and Swagger UI. ### Falcon [Falcon](https://falconframework.org) is a minimalist REST and app backend framework for Python, with a focus on reliability, correctness, and performance at scale. ### Muffin [Muffin](https://github.com/klen/muffin) is a fast, lightweight and asynchronous ASGI web-framework for Python 3. ### Litestar [Litestar](https://litestar.dev) is a powerful, lightweight and flexible ASGI framework. It includes everything that's needed to build modern APIs - from data serialization and validation to websockets, ORM integration, session management, authentication and more. ### Panther [Panther](https://PantherPy.github.io/) is a fast & friendly web framework for building async APIs with Python 3.10+. It has built-in Document-oriented Database, Caching System, Authentication and Permission Classes, Visual API Monitoring and also supports Websocket, Throttling, Middlewares. --- ## File: docs/concepts/event-loop.md # Event Loop Uvicorn provides two event loop implementations that you can choose from using the [`--loop`](../settings.md#implementation) option: ```bash uvicorn main:app --loop ``` By default, Uvicorn uses `--loop auto`, which automatically selects: 1. **uvloop** - If [uvloop](https://github.com/MagicStack/uvloop) is installed, Uvicorn will use it for maximum performance 2. **asyncio** - If uvloop is not available, Uvicorn falls back to Python's built-in asyncio event loop Since `uvloop` is not compatible with Windows or PyPy, it is not available on these platforms. On Windows, the asyncio implementation uses the standard [`ProactorEventLoop`][asyncio.ProactorEventLoop] in single-process mode. When running with `--reload` or multiple workers, it uses [`SelectorEventLoop`][asyncio.SelectorEventLoop] instead. ??? info "Why can `ProactorEventLoop` fail with multiple processes on Windows?" If you want to know more about it, you can read the issue [#cpython/122240](https://github.com/python/cpython/issues/122240). ## Custom Event Loop You can use custom event loop implementations by specifying a module path and function name using the colon notation: ```bash uvicorn main:app --loop : ``` The function should return a callable that creates a new event loop instance. ### rloop [rloop](https://github.com/gi0baro/rloop) is an experimental AsyncIO event loop implemented in Rust on top of the [mio](https://github.com/tokio-rs/mio) crate. It aims to provide high performance through Rust's systems programming capabilities. You can install it with: === "pip" ```bash pip install rloop ``` === "uv" ```bash uv add rloop ``` You can run `uvicorn` with `rloop` with the following command: ```bash uvicorn main:app --loop rloop:new_event_loop ``` !!! warning "Experimental" rloop is currently **experimental** and **not suited for production usage**. It is only available on **Unix systems**. ### Winloop [Winloop](https://github.com/Vizonex/Winloop) is an alternative library that brings uvloop-like performance to Windows. Since uvloop is based on libuv and doesn't support Windows, Winloop provides a Windows-compatible implementation with significant performance improvements over the standard Windows event loop policies. You can install it with: === "pip" ```bash pip install winloop ``` === "uv" ```bash uv add winloop ``` You can run `uvicorn` with `Winloop` with the following command: ```bash uvicorn main:app --loop winloop:new_event_loop ``` --- ## File: docs/concepts/http-protocols.md # HTTP Protocols Uvicorn provides three HTTP/1.1 protocol implementations that you can choose from using the [`--http`](../settings.md#implementation) option: ```bash uvicorn main:app --http ``` By default, Uvicorn uses `--http auto`, which automatically selects: 1. **httptools** - If [httptools](https://github.com/MagicStack/httptools) is installed, Uvicorn will use it for maximum performance 2. **h11** - If httptools is not available, Uvicorn falls back to [h11](https://github.com/python-hyper/h11) ## h11 [h11](https://github.com/python-hyper/h11) is a pure Python HTTP/1.1 implementation. It is a required dependency of Uvicorn, so it is always available, and it is the only implementation compatible with PyPy. ## httptools [httptools](https://github.com/MagicStack/httptools) is a Python binding for the Node.js HTTP parser. It is installed as part of the `uvicorn[standard]` optional extras, and provides greater performance than h11, but is not compatible with PyPy. ## zttp [zttp](https://zttp.marcelotryle.com/) is a sans-IO HTTP parser for Python with a core written in Zig. Prebuilt wheels are available for CPython on Linux, macOS and Windows. You can install it with: === "pip" ```bash pip install zttp ``` === "uv" ```bash uv add zttp ``` You can run `uvicorn` with `zttp` with the following command: ```bash uvicorn main:app --http zttp ``` !!! warning "Experimental" zttp support is currently **experimental** and **not suited for production usage**. If you try it out, please report any issues or feedback on the [issue tracker](https://github.com/Kludex/uvicorn/issues). --- ## File: docs/concepts/lifespan.md Since Uvicorn is an ASGI server, it supports the [ASGI lifespan protocol](https://asgi.readthedocs.io/en/latest/specs/lifespan.html). This allows you to run **startup** and **shutdown** events for your application. The lifespan protocol is useful for initializing resources that need to be available throughout the lifetime of the application, such as database connections, caches, or other services. Keep in mind that the lifespan is executed **only once per application instance**. If you have multiple workers, each worker will execute the lifespan independently. ## Lifespan Architecture The lifespan protocol runs as a sibling task alongside your main application, allowing both to execute concurrently. Let's see how Uvicorn handles the lifespan and main application tasks: ```mermaid sequenceDiagram participant Server as Uvicorn Server participant LifespanTask as Lifespan Task participant AppTask as Application Task participant UserApp as User Application Note over Server: ✅ Server starts Server->>+LifespanTask: spawn_task(lifespan_handler) LifespanTask->>UserApp: {"type": "lifespan.startup"} Note over UserApp: Initialize databases, caches, etc. UserApp-->>LifespanTask: {"type": "lifespan.startup.complete"} LifespanTask->>Server: ✅ Startup complete Server->>+AppTask: spawn_task(application_handler) Note over AppTask: ✅ Ready for requests rect rgb(240, 248, 255) Note over LifespanTask, AppTask: Both tasks running concurrently par Lifespan maintains state LifespanTask->>LifespanTask: Keep lifespan connection alive and Application serves requests AppTask->>UserApp: HTTP/WebSocket requests UserApp-->>AppTask: Responses end end Note over Server: Shutdown signal received Server->>AppTask: Stop accepting new connections AppTask->>AppTask: Complete pending requests LifespanTask->>UserApp: {"type": "lifespan.shutdown"} Note over UserApp: Cleanup databases, caches, etc. UserApp-->>LifespanTask: {"type": "lifespan.shutdown.complete"} LifespanTask->>-Server: Lifespan task complete AppTask->>-Server: Application task complete Note over Server: ✅ Server stopped ``` Having the lifespan task run as a sibling task is a deliberate design choice. It could have been implemented as a parent task that spawns the application task. This decision has the implication that if you create a [`ContextVar`][contextvars.ContextVar] in the lifespan task, it will not be available in the application task. ## Usage Let's see an example of a minimal (but complete) ASGI application that implements the lifespan protocol: ```python title="ASGI application with lifespan" hl_lines="3-11" async def app(scope, receive, send): if scope['type'] == 'lifespan': while True: message = await receive() if message['type'] == 'lifespan.startup': print("Application is starting up...") await send({'type': 'lifespan.startup.complete'}) elif message['type'] == 'lifespan.shutdown': print("Application is shutting down...") await send({'type': 'lifespan.shutdown.complete'}) return elif 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!'}) else: raise RuntimeError("This server doesn't support WebSocket.") ``` You can run the above application with `uvicorn main:app`. Then you'll see the print statements when the application starts. You can also try to send some HTTP requests to it, and it will respond with "Hello, World!". And if you stop the server (`CTRL + C`), it will print `"Application is shutting down..."`. ## Disabling Lifespan If you want to disable the lifespan protocol, you can do so by setting the `lifespan` option to `off` when running Uvicorn: ```bash uvicorn main:app --lifespan off ``` By default, Uvicorn will automatically enable the lifespan protocol if the application supports it. --- ## File: docs/concepts/logging.md Uvicorn uses Python's built-in [`logging`](https://docs.python.org/3/library/logging.html) module, and provides three loggers out of the box: | Logger name | Purpose | |------------------|----------------------------------------------------| | `uvicorn` | Parent logger (rarely used directly) | | `uvicorn.error` | Server-level messages (startup, shutdown, errors) | | `uvicorn.access` | Per-request access log lines | !!! note Despite its name, `uvicorn.error` is **not** limited to error messages. It is the general-purpose server logger, similar to how Gunicorn names its main logger. See [#562](https://github.com/encode/uvicorn/issues/562) for background. ## Default Configuration By default, Uvicorn applies the following [`dictConfig()`](https://docs.python.org/3/library/logging.config.html#logging.config.dictConfig) configuration: ```python LOGGING_CONFIG = { "version": 1, "disable_existing_loggers": False, "formatters": { "default": { "()": "uvicorn.logging.DefaultFormatter", "fmt": "%(levelprefix)s %(message)s", "use_colors": None, }, "access": { "()": "uvicorn.logging.AccessFormatter", "fmt": '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', }, }, "handlers": { "default": { "formatter": "default", "class": "logging.StreamHandler", "stream": "ext://sys.stderr", }, "access": { "formatter": "access", "class": "logging.StreamHandler", "stream": "ext://sys.stdout", }, }, "loggers": { "uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False}, "uvicorn.error": {"level": "INFO"}, "uvicorn.access": {"handlers": ["access"], "level": "INFO", "propagate": False}, }, } ``` ## Custom Logging Configuration You can supply a custom logging configuration file with the `--log-config` option (or `log_config` when calling `uvicorn.run()`). Uvicorn supports three file formats: | Extension | Loader | Notes | |----------------|------------------------------|---------------------------------------------| | `.json` | `logging.config.dictConfig` | Standard JSON `dictConfig` schema. | | `.yaml`/`.yml` | `logging.config.dictConfig` | Requires **PyYAML** (`uvicorn[standard]`). | | Any other | `logging.config.fileConfig` | Classic INI-style format. | ### YAML Example Create a file named `log_config.yaml`: ```yaml version: 1 disable_existing_loggers: false formatters: default: "()": uvicorn.logging.DefaultFormatter fmt: "%(asctime)s - %(levelprefix)s %(message)s" datefmt: "%Y-%m-%d %H:%M:%S" use_colors: null access: "()": uvicorn.logging.AccessFormatter fmt: '%(asctime)s - %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s' datefmt: "%Y-%m-%d %H:%M:%S" handlers: default: formatter: default class: logging.StreamHandler stream: ext://sys.stderr access: formatter: access class: logging.StreamHandler stream: ext://sys.stdout loggers: uvicorn: handlers: - default level: INFO propagate: false uvicorn.error: level: INFO uvicorn.access: handlers: - access level: INFO propagate: false ``` Then pass it to Uvicorn: === "CLI" ```bash uvicorn main:app --log-config log_config.yaml ``` === "Programmatic" ```python uvicorn.run("main:app", log_config="log_config.yaml") ``` ### JSON Example Create a file named `log_config.json`: ```json { "version": 1, "disable_existing_loggers": false, "formatters": { "default": { "()": "uvicorn.logging.DefaultFormatter", "fmt": "%(asctime)s - %(levelprefix)s %(message)s", "datefmt": "%Y-%m-%d %H:%M:%S", "use_colors": null }, "access": { "()": "uvicorn.logging.AccessFormatter", "fmt": "%(asctime)s - %(levelprefix)s %(client_addr)s - \"%(request_line)s\" %(status_code)s", "datefmt": "%Y-%m-%d %H:%M:%S" } }, "handlers": { "default": { "formatter": "default", "class": "logging.StreamHandler", "stream": "ext://sys.stderr" }, "access": { "formatter": "access", "class": "logging.StreamHandler", "stream": "ext://sys.stdout" } }, "loggers": { "uvicorn": { "handlers": ["default"], "level": "INFO", "propagate": false }, "uvicorn.error": { "level": "INFO" }, "uvicorn.access": { "handlers": ["access"], "level": "INFO", "propagate": false } } } ``` ### Programmatic `dictConfig` You can also pass a dictionary directly when running programmatically: ```python import uvicorn log_config = { "version": 1, "disable_existing_loggers": False, "formatters": { "default": { "()": "uvicorn.logging.DefaultFormatter", "fmt": "%(asctime)s - %(levelprefix)s %(message)s", "datefmt": "%Y-%m-%d %H:%M:%S", }, "access": { "()": "uvicorn.logging.AccessFormatter", "fmt": '%(asctime)s - %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', "datefmt": "%Y-%m-%d %H:%M:%S", }, }, "handlers": { "default": { "formatter": "default", "class": "logging.StreamHandler", "stream": "ext://sys.stderr", }, "access": { "formatter": "access", "class": "logging.StreamHandler", "stream": "ext://sys.stdout", }, }, "loggers": { "uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False}, "uvicorn.error": {"level": "INFO"}, "uvicorn.access": {"handlers": ["access"], "level": "INFO", "propagate": False}, }, } uvicorn.run("main:app", log_config=log_config) ``` ## Common Recipes ### Writing Logs to a File To write Uvicorn's server logs to a file in addition to the console, add a `FileHandler` to the `uvicorn` logger: ```yaml version: 1 disable_existing_loggers: false formatters: default: "()": uvicorn.logging.DefaultFormatter fmt: "%(asctime)s - %(levelprefix)s %(message)s" datefmt: "%Y-%m-%d %H:%M:%S" use_colors: false access: "()": uvicorn.logging.AccessFormatter fmt: '%(asctime)s - %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s' datefmt: "%Y-%m-%d %H:%M:%S" handlers: default: formatter: default class: logging.StreamHandler stream: ext://sys.stderr access: formatter: access class: logging.StreamHandler stream: ext://sys.stdout file: formatter: default class: logging.FileHandler filename: uvicorn.log loggers: uvicorn: handlers: - default - file level: INFO propagate: false uvicorn.error: level: INFO uvicorn.access: handlers: - access level: INFO propagate: false ``` In this example, `uvicorn.access` still writes to stdout only. To write access logs to the file as well, add `file` to the `uvicorn.access.handlers` list. ### Disabling Access Logs Use the `--no-access-log` CLI flag, or set `access_log=False` programmatically. This removes all handlers from `uvicorn.access` without affecting the `uvicorn.error` logger. ### Disabling Colors Pass `--no-use-colors` on the command line, or set `use_colors=False` programmatically. When using a custom `--log-config`, set `use_colors: false` on each formatter that extends `uvicorn.logging.ColourizedFormatter`. ### Using a Standard Formatter If you do not need Uvicorn's colorized output, you can use the standard `logging.Formatter` instead: ```yaml version: 1 disable_existing_loggers: false formatters: default: format: "%(asctime)s [%(levelname)s] %(name)s: %(message)s" datefmt: "%Y-%m-%d %H:%M:%S" handlers: default: formatter: default class: logging.StreamHandler stream: ext://sys.stderr loggers: uvicorn: handlers: - default level: INFO propagate: false uvicorn.error: level: INFO uvicorn.access: handlers: - default level: INFO propagate: false ``` !!! warning When using a standard `logging.Formatter` for the access logger, the `%(client_addr)s`, `%(request_line)s`, and `%(status_code)s` placeholders are **not** available. The access log line will be formatted using only the standard `%(message)s` field. --- ## File: docs/concepts/websockets.md **Uvicorn** supports the WebSocket protocol as defined in [RFC 6455](https://datatracker.ietf.org/doc/html/rfc6455). ## Upgrade Process The WebSocket protocol starts as an HTTP connection that gets "upgraded" to a WebSocket connection through a handshake process. Here's how it works: ```mermaid sequenceDiagram participant Client participant Server participant ASGI App Note over Client,ASGI App: WebSocket Handshake Process Client->>Server: HTTP GET Request Note right of Client: Headers:Upgrade: websocketConnection: UpgradeSec-WebSocket-Key: [key]Sec-WebSocket-Version: 13 Server->>ASGI App: websocket.connect event Note right of Server: Scope type: "websocket" alt Connection Accepted ASGI App->>Server: {"type": "websocket.accept"} Server->>Client: HTTP 101 Switching Protocols Note right of Server: Headers:Upgrade: websocketConnection: UpgradeSec-WebSocket-Accept: [hash] Note over Client,ASGI App: WebSocket Connection Established loop Message Exchange Client->>Server: WebSocket Frame Server->>ASGI App: websocket.receive event ASGI App->>Server: {"type": "websocket.send", "text": "..."} Server->>Client: WebSocket Frame end alt Client Closes Client->>Server: Close Frame Server->>ASGI App: websocket.disconnect event else Server Closes ASGI App->>Server: {"type": "websocket.close"} Server->>Client: Close Frame end else Connection Rejected ASGI App->>Server: {"type": "websocket.http.response.start", "status": 403} Server->>Client: HTTP 403 Forbidden end ``` 1. **Initial HTTP Request**: The client sends a regular HTTP GET request with special headers indicating it wants to upgrade to WebSocket: - `Upgrade: websocket` - `Connection: Upgrade` - `Sec-WebSocket-Key`: A base64-encoded random key - `Sec-WebSocket-Version: 13` 2. **Server Processing**: Uvicorn receives the request and creates a WebSocket scope, sending a `websocket.connect` event to the ASGI application. 3. **Application Decision**: The ASGI app decides whether to accept or reject the connection based on authentication, authorization, or other logic. 4. **Handshake Completion**: If accepted, the server responds with HTTP 101 status and the computed `Sec-WebSocket-Accept` header. 5. **Full-Duplex Communication**: Once upgraded, both client and server can send messages at any time using WebSocket frames. 6. **Connection Termination**: Either side can initiate closing the connection with a close frame. ## ASGI WebSocket Events **Uvicorn** translates WebSocket protocol messages into ASGI events: - `websocket.connect`: Sent when a client requests a WebSocket upgrade - `websocket.receive`: Sent when a message is received from the client - `websocket.disconnect`: Sent when the connection is closed The ASGI app can respond with: - `websocket.accept`: Accept the connection upgrade with an optional subprotocol - `websocket.send`: Send a message to the client - `websocket.close`: Close the connection with an optional status code You can read more about it on the [ASGI documentation](https://asgi.readthedocs.io/en/latest/specs/www.html#websocket). ## Protocol Implementations **Uvicorn** has three implementations of the WebSocket protocol. ### WSProto Protocol This implementation was the first implemented. It uses the [`wsproto`](https://python-hyper.org/projects/wsproto/en/stable/) package underneath. You can choose this protocol by setting the `--ws` option to `wsproto`. ### WebSocket Protocol This implementation uses the [`websockets`](https://websockets.readthedocs.io/) package as dependency. By default, if you have `websockets` installed, Uvicorn will use this protocol. ### WebSockets SansIO Protocol Since `websockets` deprecated the API Uvicorn uses to run the previous protocol, we had to create this new protocol that uses the `websockets` SansIO API. You can choose this protocol by setting the `--ws` option to `websockets-sansio`. !!! note The SansIO implementation was released in Uvicorn version 0.35.0 in June 2025. --- ## File: docs/deployment/docker.md # Dockerfile **Docker** is a popular choice for modern application deployment. However, creating a good Dockerfile from scratch can be challenging. This guide provides a **solid foundation** that works well for most Python projects. While the example below won't fit every use case, it offers an excellent starting point that you can adapt to your specific needs. ## Quickstart For this example, we'll need to install [`docker`](https://docs.docker.com/get-docker/), [docker-compose](https://docs.docker.com/compose/install/) and [`uv`](https://docs.astral.sh/uv/getting-started/installation/). Then, let's create a new project with `uv`: ```bash uv init app ``` This will create a new project with a basic structure: ```bash app/ ├── main.py ├── pyproject.toml └── README.md ``` On `main.py`, let's create a simple ASGI application: ```python title="main.py" async def app(scope, receive, send): body = "Hello, world!" await send( { "type": "http.response.start", "status": 200, "headers": [ [b"content-type", b"text/plain"], [b"content-length", len(body)], ], } ) await send( { "type": "http.response.body", "body": body.encode("utf-8"), } ) ``` We need to include `uvicorn` in the dependencies: ```bash uv add uvicorn ``` This will also create a `uv.lock` file. :sunglasses: ??? tip "What is `uv.lock`?" `uv.lock` is a `uv` specific lockfile. A lockfile is a file that contains the exact versions of the dependencies that were installed when the `uv.lock` file was created. This allows for deterministic builds and consistent deployments. Just to make sure everything is working, let's run the application: ```bash uv run uvicorn main:app ``` You should see the following output: ```bash INFO: Started server process [62727] INFO: Waiting for application startup. INFO: ASGI 'lifespan' protocol appears unsupported. INFO: Application startup complete. INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` ## Dockerfile We'll create a **cache-aware Dockerfile** that optimizes build times. The key strategy is to install dependencies first, then copy the project files. This approach leverages Docker's caching mechanism to significantly speed up rebuilds. ```dockerfile title="Dockerfile" FROM python:3.12-slim COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ # Change the working directory to the `app` directory WORKDIR /app # Install dependencies RUN --mount=type=cache,target=/root/.cache/uv \ --mount=type=bind,source=uv.lock,target=uv.lock \ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ uv sync --frozen --no-install-project # Copy the project into the image ADD . /app # Sync the project RUN --mount=type=cache,target=/root/.cache/uv \ uv sync --frozen # Run with uvicorn CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] ``` A common question is **"how many workers should I run?"**. The image above uses a single Uvicorn worker. The recommended approach is to let your orchestration system manage the number of deployed containers rather than relying on the process manager inside the container. You can read more about this in the [Decouple applications](https://docs.docker.com/build/building/best-practices/#decouple-applications) section of the Docker documentation. !!! warning "For production, create a non-root user!" When running in production, you should create a non-root user and run the container as that user. To make sure it works, let's build the image and run it: ```bash docker build -t my-app . docker run -p 8000:8000 my-app ``` For more information on using uv with Docker, refer to the [official uv Docker integration guide](https://docs.astral.sh/uv/guides/integration/docker/). ## Docker Compose When running in development, it's often useful to have a way to hot-reload the application when code changes. Let's create a `docker-compose.yml` file to run the application: ```yaml title="docker-compose.yml" services: backend: build: . ports: - "8000:8000" environment: - UVICORN_RELOAD=true volumes: - .:/app tty: true ``` You can run the application with `docker compose up` and it will automatically rebuild the image when code changes. Now you have a fully working development environment! :tada: --- ## File: docs/deployment/index.md Server deployment is a complex area, that will depend on what kind of service you're deploying Uvicorn onto. As a general rule, you probably want to: * Run `uvicorn --reload` from the command line for local development. * Run `gunicorn -k uvicorn.workers.UvicornWorker` for production. * Additionally run behind Nginx for self-hosted deployments. * Finally, run everything behind a CDN for caching support, and serious DDOS protection. ## Running from the command line Typically you'll run `uvicorn` from the command line. ```bash $ uvicorn main:app --reload --port 5000 ``` The ASGI application should be specified in the form `path.to.module:instance.path`. When running locally, use `--reload` to turn on auto-reloading. The `--reload` and `--workers` arguments are **mutually exclusive**. To see the complete set of available options, use `uvicorn --help`. See the [settings documentation](../settings.md) for more details on the supported options for running uvicorn. ## Running programmatically To run directly from within a Python program, you should use `uvicorn.run(app, **config)`. For example: ```py title="main.py" import uvicorn class App: ... app = App() if __name__ == "__main__": uvicorn.run("main:app", host="127.0.0.1", port=5000, log_level="info") ``` The set of configuration options is the same as for the command line tool. Note that the application instance itself *can* be passed instead of the app import string. ```python uvicorn.run(app, host="127.0.0.1", port=5000, log_level="info") ``` However, this style only works if you are not using multiprocessing (`workers=NUM`) or reloading (`reload=True`), so we recommend using the import string style. Also note that in this case, you should put `uvicorn.run` into `if __name__ == '__main__'` clause in the main module. !!! note The `reload` and `workers` parameters are **mutually exclusive**. ## Using a process manager Running Uvicorn using a process manager ensures that you can run multiple processes in a resilient manner, and allows you to perform server upgrades without dropping requests. A process manager will handle the socket setup, start-up multiple server processes, monitor process aliveness, and listen for signals to provide for processes restarts, shutdowns, or dialing up and down the number of running processes. ### Built-in Uvicorn includes a `--workers` option that allows you to run multiple worker processes. ```bash $ uvicorn main:app --workers 4 ``` Unlike gunicorn, uvicorn does not use pre-fork, but uses [`spawn`](https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods), which allows uvicorn's multiprocess manager to still work well on Windows. The default process manager monitors the status of child processes and automatically restarts child processes that die unexpectedly. Not only that, it will also monitor the status of the child process through the pipeline. When the child process is accidentally stuck, the corresponding child process will be killed through an unstoppable system signal or interface. You can also manage child processes by sending specific signals to the main process. (Not supported on Windows.) - `SIGHUP`: Gracefully restart the workers one at a time with no dropped requests. Fresh workers pick up new code on disk. - `SIGTTIN`: Increase the number of worker processes by one. - `SIGTTOU`: Decrease the number of worker processes by one. ### Gunicorn !!! warning The `uvicorn.workers` module is deprecated and will be removed in a future release. You should use the [`uvicorn-worker`](https://github.com/Kludex/uvicorn-worker) package instead. ```bash python -m pip install uvicorn-worker ``` Gunicorn is probably the simplest way to run and manage Uvicorn in a production setting. Uvicorn includes a gunicorn worker class that means you can get set up with very little configuration. The following will start Gunicorn with four worker processes: `gunicorn -w 4 -k uvicorn.workers.UvicornWorker` The `UvicornWorker` implementation uses the `uvloop` and `httptools` implementations. To run under PyPy you'll want to use pure-python implementation instead. You can do this by using the `UvicornH11Worker` class. `gunicorn -w 4 -k uvicorn.workers.UvicornH11Worker` Gunicorn provides a different set of configuration options to Uvicorn, so some options such as `--limit-concurrency` are not yet supported when running with Gunicorn. If you need to pass uvicorn's config arguments to gunicorn workers then you'll have to subclass `UvicornWorker`: ```python from uvicorn.workers import UvicornWorker class MyUvicornWorker(UvicornWorker): CONFIG_KWARGS = {"loop": "asyncio", "http": "h11", "lifespan": "off"} ``` ### Supervisor To use `supervisor` as a process manager you should either: * Hand over the socket to uvicorn using its file descriptor, which supervisor always makes available as `0`, and which must be set in the `fcgi-program` section. * Or use a UNIX domain socket for each `uvicorn` process. A simple supervisor configuration might look something like this: ```ini title="supervisord.conf" [supervisord] [fcgi-program:uvicorn] socket=tcp://localhost:8000 command=venv/bin/uvicorn --fd 0 main:App numprocs=4 process_name=uvicorn-%(process_num)d stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 ``` Then run with `supervisord -n`. ## Running behind Nginx Using Nginx as a proxy in front of your Uvicorn processes may not be necessary, but is recommended for additional resilience. Nginx can deal with serving your static media and buffering slow requests, leaving your application servers free from load as much as possible. In managed environments such as `Heroku`, you won't typically need to configure Nginx, as your server processes will already be running behind load balancing proxies. The recommended configuration for proxying from Nginx is to use a UNIX domain socket between Nginx and whatever the process manager that is being used to run Uvicorn. If using Uvicorn directly you can bind it to a UNIX domain socket using `uvicorn --uds /path/to/socket.sock <...>`. When running your application behind one or more proxies you will want to make sure that each proxy sets appropriate headers to ensure that your application can properly determine the client address of the incoming connection, and if the connection was over `http` or `https`. For more information see [Proxies and Forwarded Headers](#proxies-and-forwarded-headers) below. Here's how a simple Nginx configuration might look. This example includes setting proxy headers, and using a UNIX domain socket to communicate with the application server. It also includes some basic configuration to forward websocket connections. For more info on this, check [Nginx recommendations](https://nginx.org/en/docs/http/websocket.html). ```conf http { server { listen 80; client_max_body_size 4G; server_name example.com; location / { proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_redirect off; proxy_buffering off; proxy_pass http://uvicorn; } location /static { # path for static files root /path/to/app/static; } } map $http_upgrade $connection_upgrade { default upgrade; '' close; } upstream uvicorn { server unix:/tmp/uvicorn.sock; } } ``` Uvicorn's `--proxy-headers` behavior may not be sufficient for more complex proxy configurations that use different combinations of headers, or where the application is running behind more than one intermediary proxying service. In those cases, you might want to use an ASGI middleware to set the `client` and `scheme` dependent on the request headers. ## Running behind a CDN Running behind a content delivery network, such as Cloudflare or Cloud Front, provides a serious layer of protection against DDoS attacks. Your service will be running behind huge clusters of proxies and load balancers that are designed for handling huge amounts of traffic, and have capabilities for detecting and closing off connections from DDoS attacks. Proper usage of cache control headers can mean that a CDN is able to serve large amounts of data without always having to forward the request on to your server. Content Delivery Networks can also be a low-effort way to provide HTTPS termination. ## Running with HTTPS To run uvicorn with https, a certificate and a private key are required. The recommended way to get them is using [Let's Encrypt](https://letsencrypt.org/). For local development with https, it's possible to use [mkcert](https://github.com/FiloSottile/mkcert) to generate a valid certificate and private key. ```bash $ uvicorn main:app --port 5000 --ssl-keyfile=./key.pem --ssl-certfile=./cert.pem ``` ### Running gunicorn worker It's also possible to use certificates with uvicorn's worker for gunicorn. ```bash $ gunicorn --keyfile=./key.pem --certfile=./cert.pem -k uvicorn.workers.UvicornWorker main:app ``` ### Customizing the SSL context For TLS scenarios that the `--ssl-*` flags don't cover (e.g., mutual TLS, custom `SSLContext.options`, bumping `minimum_version`, loading certificates from memory), pass an `ssl_context_factory` to `uvicorn.run()` or `Config`. The factory receives the `Config` instance and a `default_ssl_context_factory` callable that builds the standard context from the `ssl_*` settings on `Config`. Use it to start from uvicorn's default and mutate it, or ignore it and build your own context from scratch - the `ssl_*` settings are only consumed by the default factory, so if you don't call it they're effectively unused. ```python import ssl from collections.abc import Callable import uvicorn from uvicorn.config import Config def ssl_context_factory(config: Config, default_ssl_context_factory: Callable[[], ssl.SSLContext]) -> ssl.SSLContext: context = default_ssl_context_factory() context.minimum_version = ssl.TLSVersion.TLSv1_3 return context uvicorn.run( "main:app", ssl_keyfile="key.pem", ssl_certfile="cert.pem", ssl_context_factory=ssl_context_factory, ) ``` The factory is called inside each worker process, so it works with `--reload` and `--workers > 1`. The factory itself must be picklable in those modes (a top-level function is fine; lambdas and local closures are not). The `ssl_*` settings on `Config` are only consumed by `default_ssl_context_factory()`; if you build the context yourself without calling it, those settings are ignored. ## Proxies and Forwarded Headers When running an application behind one or more proxies, certain information about the request is lost. To avoid this most proxies will add headers containing this information for downstream servers to read. Uvicorn currently supports the following headers: - `X-Forwarded-For` ([MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For)) - `X-Forwarded-Proto`([MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto)) Uvicorn can use these headers to correctly set the client and protocol in the request. However as anyone can set these headers you must configure which "clients" you will trust to have set them correctly. Uvicorn can be configured to trust IP Addresses (e.g. `127.0.0.1`), IP Networks (e.g. `10.100.0.0/16`), or Literals (e.g. `/path/to/socket.sock`). When running from CLI these are configured using `--forwarded-allow-ips`. !!! Warning "Only trust clients you can actually trust!" Incorrectly trusting other clients can lead to malicious actors spoofing their apparent client address to your application. A proxy chain may send a header once per hop rather than as a single comma-separated value. Repeated `X-Forwarded-For` fields are combined in order (as the equivalent comma-separated list, [RFC 9110, 5.3](https://www.rfc-editor.org/rfc/rfc9110#section-5.3)), while for `X-Forwarded-Proto` the last field is used. For more information, check [`ProxyHeadersMiddleware`](https://github.com/Kludex/uvicorn/blob/main/uvicorn/middleware/proxy_headers.py). ### Client Port Currently if the `ProxyHeadersMiddleware` is able to retrieve a trusted client value then the client's port will be set to `0`. This is because port information is lost when using these headers. ### UNIX Domain Sockets (UDS) Although it is common for UNIX Domain Sockets to be used for communicating between various HTTP servers, they can mess with some of the expected received values as they will be various non-address strings or missing values. For example: - when NGINX itself is running behind a UDS it will add the literal `unix:` as the client in the `X-Forwarded-For` header. - When Uvicorn is running behind a UDS the initial client will be `None`. ### Trust Everything Rather than specifying what to trust, you can instruct Uvicorn to trust all clients using the literal `"*"`. You should only set this when you know you can trust all values within the forwarded headers (e.g. because your proxies remove the existing headers before setting their own). --- ## File: docs/contributing.md # Contributing Thank you for being interested in contributing to Uvicorn. There are many ways you can contribute to the project: - Using Uvicorn on your stack and [reporting bugs/issues you find](https://github.com/Kludex/uvicorn/issues/new) - [Implementing new features and fixing bugs](https://github.com/Kludex/uvicorn/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) - [Review Pull Requests of others](https://github.com/Kludex/uvicorn/pulls) - Write documentation - Participate in discussions ## Reporting Bugs, Issues or Feature Requests Found something that Uvicorn should support? Stumbled upon some unexpected behaviour? Need a missing functionality? Contributions should generally start out from a previous discussion. You can reach out someone at the [community chat](https://discord.com/invite/SWU73HffbV) or at the [github discussions tab](https://github.com/Kludex/uvicorn/discussions). When creating a new topic in the discussions tab, possible bugs may be raised as a "Potential Issue" discussion, feature requests may be raised as an "Ideas" discussion. We can then determine if the discussion needs to be escalated into an "Issue" or not, or if we'd consider a pull request. Try to be more descriptive as you can and in case of a bug report, provide as much information as possible like: - OS platform - Python version - Installed dependencies and versions (`python -m pip freeze`) - Code snippet - Error traceback You should always try to reduce any examples to the *simplest possible case* that demonstrates the issue. Some possibly useful tips for narrowing down potential issues... - Does the issue exist with a specific supervisor like `Multiprocess` or more than one? - Does the issue exist on asgi, or wsgi, or both? - Are you running Uvicorn in conjunction with Gunicorn, others, or standalone? ## Development To start developing Uvicorn create a **fork** of the [Uvicorn repository](https://github.com/Kludex/uvicorn) on GitHub. Then clone your fork with the following command replacing `YOUR-USERNAME` with your GitHub username: ```shell $ git clone https://github.com/YOUR-USERNAME/uvicorn ``` You can now install the project and its dependencies using: ```shell $ cd uvicorn $ scripts/install ``` ## Testing and Linting We use custom shell scripts to automate testing, linting, and documentation building workflow. To run the tests, use: ```shell $ scripts/test ``` Any additional arguments will be passed to `pytest`. See the [pytest documentation](https://docs.pytest.org/en/latest/how-to/usage.html) for more information. For example, to run a single test script: ```shell $ scripts/test tests/test_cli.py ``` To run the code auto-formatting: ```shell $ scripts/lint ``` Lastly, to run code checks separately (they are also run as part of `scripts/test`), run: ```shell $ scripts/check ``` ## Documenting Documentation pages are located under the `docs/` folder. To run the documentation site locally (useful for previewing changes), use: ```shell $ scripts/docs serve ``` ## Resolving Build / CI Failures Once you've submitted your pull request, the test suite will automatically run, and the results will show up in GitHub. If the test suite fails, you'll want to click through to the "Details" link, and try to identify why the test suite failed. Here are some common ways the test suite can fail: ### Check Job Failed This job failing means there is either a code formatting issue or type-annotation issue. You can look at the job output to figure out why it's failed or within a shell run: ```shell $ scripts/check ``` It may be worth it to run `$ scripts/lint` to attempt auto-formatting the code and if that job succeeds commit the changes. ### Docs Job Failed This job failing means the documentation failed to build. This can happen for a variety of reasons like invalid markdown or missing configuration within `mkdocs.yml`. ### Python 3.X Job Failed This job failing means the unit tests failed or not all code paths are covered by unit tests. If tests are failing you will see this message under the coverage report: `=== 1 failed, 354 passed, 1 skipped, 1 xfailed in 37.08s ===` If tests succeed but coverage doesn't reach 100%, you will see this message under the coverage report: `Coverage failure: total of 98 is less than fail-under=100` ## Releasing *This section is targeted at Uvicorn maintainers.* Before releasing a new version, create a pull request that includes: - **An update to the changelog**: - We follow the format from [keepachangelog](https://keepachangelog.com/en/1.0.0/). - [Compare](https://github.com/Kludex/uvicorn/compare/) `main` with the tag of the latest release, and list all entries that are of interest to our users: - Things that **must** go in the changelog: added, changed, deprecated or removed features, and bug fixes. - Things that **should not** go in the changelog: changes to documentation, tests or tooling. - Try sorting entries in descending order of impact / importance. - Keep it concise and to-the-point. 🎯 - **A version bump**: see `__init__.py`. For an example, see [#1006](https://github.com/Kludex/uvicorn/pull/1107). Once the release PR is merged, create a [new release](https://github.com/Kludex/uvicorn/releases/new) including: - Tag version like `0.13.3`. - Release title `Version 0.13.3` - Description copied from the changelog. Once created this release will be automatically uploaded to PyPI.