Search Topics
Search across all FastAPI topics
ASGI vs WSGI
Under the HoodYour 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.
$ 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
# 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 returnsASGI: 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!
# 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 loopwsgi 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
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:
# 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.
# 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)from some_wsgi_middleware import WSGIMiddleware
# This wraps your ASGI app in WSGI — breaks async!
app = WSGIMiddleware(app)