Fading Coder

An Old Coder’s Final Dance

Home > Tech > Content

Running Python ASGI Apps with Uvicorn: Features, Configuration, and Deployment

Tech 2

Overview

Uvicorn is a high‑performance ASGI server that emphasizes simplicity and speed. It builds on event-loop and HTTP parsing libraries optimized in C, making it a strong choice for serving modern asynchronous Python web applications.

  • uvloop: A drop-in replacement for Python’s default asyncio event loop, implemented in Cython and typically 2–4× faster than the stock loop.
  • httptools: Python bindings to the Node.js HTTP parser, providing fast HTTP/1.1 parsing.

ASGI in a Nutshell

ASGI (Asynchronous Server Gateway Interface) is the standard interface between async Python applications and servers. It enables servers and frameworks to interoperate over protocols like HTTP/1.1 and WebSocket, and is the successor to WSGI in asynchronous conteexts.

What Uvicorn Supports

  • HTTP/1.1
  • WebSocket
  • HTTP/2 support is not native at the time of writing; check the Uvicorn changelog for updates

Installation

pip install uvicorn

Minimal ASGI Application

Create example.py with a small ASGI 3 application:

# example.py

async def app(scope, receive, send):
    if scope["type"] != "http":
        return

    body = b"Hello from ASGI via Uvicorn"
    headers = [
        (b"content-type", b"text/plain"),
        (b"content-length", str(len(body)).encode()),
    ]

    await send({
        "type": "http.response.start",
        "status": 200,
        "headers": headers,
    })
    await send({
        "type": "http.response.body",
        "body": body,
    })

Start the server from the command line:

uvicorn example:app

Starting Uvicorn Programmatically

# run_app.py
import uvicorn

async def app(scope, receive, send):
    ...  # your ASGI app

if __name__ == "__main__":
    uvicorn.run("example:app", host="127.0.0.1", port=5000, log_level="info")

Note: When using features like reload or multiple workers, pass the application as an import string ("module:attribute").

Using Uvicorn with FastAPI

# main.py
from fastapi import FastAPI

api = FastAPI()

@api.get("/")
async def hello():
    return {"message": "Hello World"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run("main:api", host="0.0.0.0", port=8000)

How uvicorn.run Handles Reload and Workers (Conceptual)

# Pseudocode — illustrates behavior, not source code

def run(app, **kwargs):
    cfg = Config(app, **kwargs)
    srv = Server(cfg)

    # Require import string for reload/workers
    if (cfg.reload or cfg.workers and cfg.workers > 1) and not isinstance(app, str):
        # log warning + exit
        raise SystemExit(1)

    if cfg.should_reload:
        sock = cfg.bind_socket()
        StatReload(cfg, target=srv.run, sockets=[sock]).run()
    elif cfg.workers and cfg.workers > 1:
        sock = cfg.bind_socket()
        Multiprocess(cfg, target=srv.run, sockets=[sock]).run()
    else:
        srv.run()

Config Highlights

Uvicorn’s Config exposes many knobs for tuning behavior. Commonly used options include:

class Config:
    def __init__(
        self,
        app,
        host: str = "127.0.0.1",
        port: int = 8000,
        loop: str = "auto",
        http: str = "auto",
        ws: str = "auto",
        lifespan: str = "auto",
        log_level: str | None = None,
        access_log: bool = True,
        reload: bool = False,
        reload_dirs: list[str] | None = None,
        workers: int | None = None,
        proxy_headers: bool = True,
        forwarded_allow_ips: str | None = None,
        root_path: str = "",
        timeout_keep_alive: int = 5,
        ssl_keyfile: str | None = None,
        ssl_certfile: str | None = None,
        headers: list[tuple[str, str]] | None = None,
        # ... many more
    ):
        ...

Programmatic example with common flags:

import uvicorn

uvicorn.run(
    "main:api",
    host="127.0.0.1",
    port=8000,
    reload=True,
    log_level="debug",
)

If you enable reload or multiple workers in code, provide the app as "module:attribute":

# Correct for reload/workers
uvicorn.run("myservice.main:app", reload=True, workers=1)

Command-Line Essentials

Useful command-line flags (subset):

  • --host, --port: bind address and port
  • --reload, --reload-dir: auto-reload on code changes
  • --workers: number of worker processes (not valid with --reload)
  • --loop [auto|asyncio|uvloop|iocp]: event loop policy
  • --http [auto|h11|httptools]: HTTP implementation
  • --ws [auto|none|websockets|wsproto]: WebSocket implementation
  • --lifespan [auto|on|off]: lifespan protocol handling
  • --interface [auto|asgi3|asgi2|wsgi]: application interface
  • --log-level, --log-config, --no-access-log
  • --proxy-headers, --forwarded-allow-ips, --root-path
  • --limit-concurrency, --limit-max-requests, --backlog
  • --timeout-keep-alive
  • --ssl-keyfile, --ssl-certfile, --ssl-ca-certs, --ssl-ciphers

Get the full list with:

uvicorn --help

Running Multiple Processes

Uvicorn can spawn multiple workers via --workers, but it does not act as a full process manager. For production, pair Uvicorn’s workers with a process manager (such as Gunicorn, Supervisor, Circus, or a container orchestration platform).

Gunicorn + Uvicorn Workers

Gunicorn is a mature process manager and web server. Uvicorn ships worker classes that plug into Gunicorn so you get Uvicorn’s performance with Gunicorn’s management features.

gunicorn example:app -w 4 -k uvicorn.workers.UvicornWorker

For environments where pure-Python is preferred (e.g., PyPy), use the H11-based worker:

gunicorn -w 4 -k uvicorn.workers.UvicornH11Worker example:app

Note: Some Uvicorn-specific switches (for example, certain concurrency limits) may not map directly to Gunicorn.

Supervisor Configuration

You can hand off the listening socket to Uvicorn via a file descriptor exposed by Supervisor. A basic configuration using an fcgi-program entry might look like this:

[supervisord]

[fcgi-program:asgi]
socket = tcp://127.0.0.1:8000
command = /path/to/venv/bin/uvicorn --fd 0 myapp.main:app
numprocs = 4
process_name = asgi-%(process_num)d
stdout_logfile = /dev/stdout
stdout_logfile_maxbytes = 0

Start Supervisor (e.g., supervisord -n) to launch the workers.

Circus Configuration

Circus can also pass an existing socket to Uvicorn:

[watcher:web]
cmd = /path/to/venv/bin/uvicorn --fd $(circus.sockets.web) myapp.main:app
use_sockets = True
numprocesses = 4

[socket:web]
host = 0.0.0.0
port = 8000

Run with:

circusd circus.ini

Reverse Proxy with Nginx

Place Nginx in front of Uvicorn for TLS termination, buffering, or load balancing. Forward the original client information so your app can reconstruct scheme and client IPs.

http {
  upstream uvicorn_upstream {
    # Example using a Unix socket; TCP endpoints also work
    server unix:/tmp/uvicorn.sock;
  }

  server {
    listen 80;
    server_name example.com;
    client_max_body_size 4G;

    location /static/ {
      alias /path/to/app/static/;
    }

    location / {
      proxy_pass http://uvicorn_upstream;
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_redirect off;
      proxy_buffering off;
    }
  }
}

When proxying, consider starting Uvicorn with --proxy-headers and set --forwarded-allow-ips appropriately so forwarded headers are trusted.

HTTPS

To serve HTTPS directly from Uvicorn, provide a certificate and private key. In production, obtain certificates via Let’s Encrypt (e.g., with certbot). For local development, tools like mkcert can generate trusted certificates.

uvicorn example:app --port 5000 \
  --ssl-keyfile ./key.pem \
  --ssl-certfile ./cert.pem

HTTPS with Gunicorn + Uvicorn Worker

gunicorn example:app \
  -k uvicorn.workers.UvicornWorker \
  --keyfile ./key.pem \
  --certfile ./cert.pem

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.