Search Topics

Search across all FastAPI topics

GitHub

ASGI vs WSGI

Under the Hood

Your FastAPI app deployed perfectly — then WebSockets failed, background tasks vanished, and async stopped working. Wrong server protocol.

You try to deploy your FastAPI app with Gunicorn using the default settings. WebSocket connections fail immediately. Background tasks never complete. Your framework expects ASGI but Gunicorn speaks WSGI.

terminal
$ gunicorn main:app

TypeError: FastAPI application received an invalid request.
Expected ASGI scope, got WSGI environ.

# Or more subtly:
# WebSocket: connection rejected (WSGI doesn't support WebSocket)
# Background tasks: silently dropped
# Async endpoints: running synchronously (no event loop!)

Question

What's the difference between WSGI and ASGI? And why does using the wrong one silently break half your app? It comes down to how your web server and your application talk to each other.

WSGI: The Old Way

WSGI (Web Server Gateway Interface) has been around since 2003. It's simple: the server calls your app with a request, your app returns a response. One request, one thread, start to finish.

Think of it like a toll booth with one lane per operator. Each operator handles one car at a time. To handle more traffic, you add more operators (threads). Flask and Django use WSGI.

Request arrives

One per thread

Thread blocked

Waiting for DB, API...

Response sent

Thread freed

Next request

Same thread, new request

wsgi_app.py
# The WSGI interface — synchronous
def application(environ, start_response):
    # environ = request data (dict)
    # start_response = callback to set status/headers
    start_response("200 OK", [("Content-Type", "text/plain")])
    return [b"Hello, World!"]
    # This function BLOCKS until it returns

ASGI: The Async Upgrade

ASGI (Asynchronous Server Gateway Interface) is the modern standard from 2018. Instead of blocking, your app can await I/O operations and let the server handle other requests in the meantime.

Think of it like a single operator managing multiple automated lanes. When a car pauses to find their card, the operator helps the next lane. One operator, many cars, no idle time. FastAPI and Starlette use ASGI.

Request arrives

Event loop picks it up

Hits await

Pauses, runs other tasks

I/O completes

Resumes where it left off

Many at once

Thousands concurrent!

asgi_app.py
# The ASGI interface — asynchronous
async def application(scope, receive, send):
    # scope = connection info (dict)
    # receive = async callable to get request body
    # send = async callable to send response
    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!",
    })
    # This function can await — never blocks the loop

wsgi vs asgi basics

What you just learned

WSGI is synchronous — one thread per request, blocks on I/O

ASGI is async — event loop handles many requests concurrently with await

FastAPI is built on ASGI — running it on WSGI breaks async, WebSockets, and more

Side by Side

WSGI

Synchronous standard (2003)

One request per thread

Threads are expensive and limited

Blocks on I/O operations

Thread sits idle waiting for database/network

No WebSocket support

HTTP only — no persistent connections

Battle-tested ecosystem

Flask, Django, and thousands of libraries

VS

ASGI

Async standard (2018)

Many requests per process

Event loop handles thousands concurrently

Yields during I/O with await

Process stays productive while waiting

WebSocket + HTTP/2 support

Full duplex, streaming, server-sent events

Growing ecosystem

FastAPI, Starlette, Django 3.0+ (partial)

See the Difference

Same 5 requests, same database latency. WSGI processes them one at a time, blocking on every I/O call. ASGI handles them all concurrently — when one request awaits the database, the event loop picks up the next.

Why FastAPI Needs ASGI

It's not just about speed. ASGI unlocks features that are fundamentally impossible with WSGI. Without ASGI, you lose the three superpowers that make FastAPI special:

main.py
# 1. Handle thousands of concurrent requests
@app.get("/users")
async def get_users():
    return await db.fetch_all()  # Yields — other requests keep flowing

# 2. Native WebSocket support
@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
    await ws.accept()
    async for message in ws.iter_text():
        await ws.send_text(f"Echo: {message}")

# 3. Server-Sent Events / Streaming
@app.get("/stream")
async def stream():
    async def generate():
        for i in range(100):
            yield f"data: {i}\n\n"
            await asyncio.sleep(0.1)
    return StreamingResponse(generate())

Watch out

The sneaky part? Running FastAPI under WSGI doesn't always give you an obvious error. Sometimes it "works" but all your async endpoints run synchronously, your WebSockets silently fail, and you have no idea why your performance is terrible. Always check your server protocol.

why asgi matters

What you just learned

ASGI enables three things WSGI can't: high concurrency, WebSockets, and streaming

Running FastAPI on WSGI silently degrades your app — async stops working

Always use an ASGI server (Uvicorn) or Gunicorn with UvicornWorker

Think about it...

Flask uses WSGI. FastAPI uses ASGI. Can you run a Flask app inside a FastAPI application?

Hint: Think about whether there's a way to translate between the two protocols.

Key Points

WSGI = Sync

One thread per request. Simple but resource-heavy under load

ASGI = Async

Event loop handles many requests per process with await

FastAPI Needs ASGI

Don't run FastAPI under WSGI — you lose all async benefits

WebSocket + Streaming

ASGI supports persistent connections that WSGI can't handle

These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.

1
Running FastAPI with a WSGI server
Using gunicorn without the Uvicorn worker class
Don't do this
terminal
# WRONG: This runs FastAPI as synchronous WSGI
gunicorn main:app

# WRONG: waitress is also WSGI
from waitress import serve
serve(app, host="0.0.0.0", port=8000)
FastAPI is an ASGI framework. Running it under a WSGI server means all your async def handlers run synchronously — await doesn't actually yield, and you lose all concurrency benefits. Always use an ASGI server.
2
Mixing sync WSGI middleware with FastAPI
Using Flask/Django WSGI middleware in an ASGI app
Don't do this
main.py
from some_wsgi_middleware import WSGIMiddleware

# This wraps your ASGI app in WSGI — breaks async!
app = WSGIMiddleware(app)
WSGI middleware is synchronous and can't handle ASGI's async lifecycle. Use Starlette-compatible ASGI middleware instead, which properly supports async request/response handling.