Gunicorn vs Uvicorn for Django

Compare Gunicorn and Uvicorn for Django: WSGI versus ASGI, when async views and WebSockets need Uvicorn, running Uvicorn workers under Gunicorn, performance for typical database-bound apps, and how to choose.

Problem statement

Gunicorn and Uvicorn are often presented as competitors, but they solve different problems. Gunicorn is a WSGI server and process manager; Uvicorn is an ASGI server that runs async applications on an event loop. Django supports both interfaces, so the real question is whether your Django app needs ASGI (async views, WebSockets via Channels, streaming), and if so, how to run Uvicorn robustly. Many production setups actually use both together: Gunicorn managing Uvicorn worker processes.

Gunicorn and Uvicorn rolesGunicornWSGI + process managerUvicornASGI event loopCombinedgunicorn -k uvicornPick bysync or async appThey are not rivals: Gunicorn manages processes, Uvicorn runs async code; they combine well.
  1. Mostly sync Django (views, ORM, templates): Gunicorn with sync or gthread workers via config.wsgi.
  2. Async views, WebSockets, SSE, or Channels: ASGI via config.asgi with Uvicorn.
  3. ASGI in production: run Uvicorn workers under Gunicorn (-k uvicorn_worker.UvicornWorker) or Uvicorn's own --workers, both behind Nginx.
  4. Performance for typical database-bound Django apps is similar; async helps when views wait on external I/O.
  5. Do not switch to ASGI only for speed; switch when you need async features.

Comparison

What each server does

  • Gunicorn implements WSGI, a synchronous interface: each worker handles one request at a time (or several with threads). It also manages processes: starting, monitoring, restarting on failure, graceful reloads on HUP.
  • Uvicorn implements ASGI, an asynchronous interface: one process runs an event loop and handles many concurrent connections, switching between them while they await I/O. It supports HTTP/1.1 and WebSockets.

Django provides both entry points (wsgi.py and asgi.py). Under ASGI, sync views still work: Django runs them in a thread pool.

what each bringsGunicornUvicornInterfaceWSGI (sync)ASGI (async)Concurrency modelprocesses, threadsevent loop per processWebSockets!noโœ“yesProcess managementโœ“mature arbiterbasic --workersBest forclassic Django appsasync views, ChannelsUse Gunicorn as the manager and Uvicorn as the worker when you need ASGIdjango-deployment.com
The combined setup gives ASGI features with Gunicorn's process supervision.

Running them together

pip install gunicorn uvicorn-worker
gunicorn config.asgi:application -k uvicorn_worker.UvicornWorker --workers 4 --bind unix:/run/gunicorn/gunicorn.sock

Gunicorn supervises the worker processes, restarts crashed ones, recycles them with max_requests, and handles graceful reloads, while each worker runs Uvicorn's event loop. Uvicorn's own --workers option also works and has improved, but Gunicorn's arbiter remains the more battle-tested process manager.

Performance in practice

A typical Django view spends most of its time in the database and in Python code, both synchronous. Under ASGI, those parts run in threads, so throughput is similar to Gunicorn with threads, sometimes slightly lower due to the thread hand-off. Async pays off when views await slow external services (APIs, LLMs) concurrently, or hold many idle connections (WebSockets, long polling, server-sent events), where an event loop handles thousands of connections per process that would each occupy a sync worker.

Operational differences

  • Long-lived connections (WebSockets) need longer proxy timeouts and different capacity planning.
  • Async code must avoid blocking calls; a sync ORM call in an async def view raises SynchronousOnlyOperation or stalls the loop.
  • Debugging and profiling async code is less familiar to many teams.
which server setup?Does the app need async features?no, classic viewsGunicorn + wsgi.pysync or gthread workersyesGunicorn + Uvicorn workersasgi.py, behind Nginxonly a few async endpointsSplit servicesWSGI app + small ASGI appAdopt ASGI for features, not for benchmark gainsdjango-deployment.com
Splitting lets most traffic stay on familiar WSGI while WebSockets get ASGI.

Explanation

WSGI was designed for a request-in, response-out model where one worker handles one request. ASGI extends Python web serving to long-lived and concurrent connections by running applications as coroutines on an event loop. Gunicorn is the most common WSGI server and also a general process manager; Uvicorn is a lean ASGI server built on uvloop and httptools. Because Gunicorn can host Uvicorn as a worker class, the combination gives ASGI capabilities with mature process management, which is why it is a common production recommendation.

Edge cases / notes

  • Channels projects can use Uvicorn or Daphne; Uvicorn is often faster for HTTP, Daphne is the Channels reference server.
  • Lifespan messages from Uvicorn ("ASGI lifespan protocol appears unsupported") are harmless with Django.
  • HTTP/2 comes from Nginx in front, not from either server.
  • Granian serves both WSGI and ASGI from a single Rust-based server, an option for new projects.

Next steps

FAQ

Should I use Gunicorn or Uvicorn for Django?

Gunicorn for typical sync Django apps; Uvicorn (usually as Gunicorn workers) when you need async views or WebSockets.

Is Uvicorn faster than Gunicorn for Django?

Not for typical database-bound views. It shines with many concurrent I/O-bound or long-lived connections.

Can Gunicorn run Uvicorn?

Yes. Use -k uvicorn_worker.UvicornWorker to run Uvicorn workers under Gunicorn's process manager.

Do sync Django views work under Uvicorn?

Yes. Django runs them in a thread pool under ASGI.

Keep going