WSGI (Web Server Gateway Interface) and ASGI (Asynchronous Server Gateway Interface) are both contracts between a Python web framework and the server that runs it. In simple language: they’re the standard way a server (gunicorn, uvicorn) and a framework (Flask, FastAPI) talk to each other.
The only difference is WSGI is sync, ASGI is async.
What WSGI looks like
WSGI was defined in PEP 3333 back in 2010. The contract is dead simple: the server hands the framework an environ dict and a start_response callable, and the framework returns an iterable of bytes.
# Minimal WSGI app
def app(environ, start_response):
start_response("200 OK", [("Content-Type", "text/plain")])
return [b"Hello WSGI"]
One request, one function call, one response. The function is sync — it returns when the response is ready. Flask and Django are WSGI frameworks. Gunicorn is a WSGI server.
The catch: there’s no way to handle long-lived connections (websockets, server-sent events) or to suspend a handler mid-execution. The interface assumes “request in, response out, done.”
What ASGI looks like
ASGI is the async successor. The contract is an async callable that receives scope, receive, and send. Now the handler can await things and yield back to the event loop.
# Minimal ASGI app
async def app(scope, receive, send):
await send({
"type": "http.response.start",
"status": 200,
"headers": [(b"content-type", b"text/plain")],
})
await send({
"type": "http.response.body",
"body": b"Hello ASGI",
})
Because send and receive are awaitable, the same protocol works for HTTP, websockets, and background events. FastAPI and Starlette are ASGI frameworks. Uvicorn and Hypercorn are ASGI servers.
Why FastAPI needs uvicorn (or similar)
FastAPI emits ASGI. Gunicorn (the standard WSGI server) can’t speak ASGI on its own. We need an ASGI server like uvicorn:
uvicorn main:app --host 0.0.0.0 --port 8000
For dev, uvicorn alone is fine. For prod, we want process management — multiple workers, graceful reloads, OS signal handling. That’s where gunicorn comes back in.
gunicorn + uvicorn workers
Gunicorn is a great process manager (forking, worker lifecycle, signals) but it’s WSGI by default. The trick: it supports plug-in worker classes, and uvicorn ships one. So we run:
gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker
What this means in simple language: gunicorn is the boss process that forks 4 child workers. Each child is actually a uvicorn event loop. Gunicorn handles restarts and signals, uvicorn handles the ASGI requests. Best of both worlds.
Modern FastAPI deploys are increasingly skipping gunicorn entirely and running uvicorn directly with --workers 4, since uvicorn now has its own multi-worker support. Both patterns work.
Interview cheat
If asked “why can’t we run FastAPI under gunicorn directly?” — because gunicorn’s default workers are WSGI, and FastAPI is ASGI. We either use -k uvicorn.workers.UvicornWorker or run uvicorn standalone.