Django ASGI Production Checklist
A checklist for running Django under ASGI in production with Uvicorn, Daphne, Granian, or Hypercorn: choose workers, avoid blocking calls in async views, handle sync middleware and the ORM, configure WebSocket proxies and timeouts, lifespan, and graceful shutdown.
Problem statement
You switched Django to ASGI for async views, streaming responses, server-sent events, or WebSockets. Under ASGI, a single blocking call in an async view can stall every request on that worker, sync middleware silently adds thread hops, the ORM requires care in async contexts, and proxies need settings for long-lived connections. Performance can end up worse than WSGI if these details are missed.
- Run an ASGI server with multiple worker processes (for example
gunicorn -k uvicorn_worker.UvicornWorkeroruvicorn --workers N, or Granian). - Keep async views free of blocking I/O: use async clients (httpx, async Redis) and the async ORM API or
sync_to_async. - Prefer async-capable middleware; each sync middleware in the chain forces thread switching.
- Configure Nginx or the load balancer for WebSocket upgrades, streaming (no buffering), and long timeouts where needed.
- Handle lifespan events and SIGTERM so in-flight requests and connections close gracefully.
Checklist
Server and workers
- ASGI server chosen and pinned: Uvicorn (with the
uvicorn-workerGunicorn class), Daphne for Channels, Granian, or Hypercorn. - Several worker processes per container or host to use multiple cores; one event loop per process.
--limit-max-requestsor equivalent recycling to bound memory growth.DJANGO_SETTINGS_MODULEandasgi.pyused consistently;get_asgi_application()called before importing models.
Async correctness
- No blocking calls in
async defviews: norequests,time.sleep, sync Redis clients, or sync ORM calls. - ORM in async code via async methods (
aget,acount,async for) or wrapped insync_to_async(thread_sensitive=True). - Sync views still work under ASGI (Django runs them in a thread pool), but heavy sync code reduces the benefit.
SynchronousOnlyOperationerrors fixed properly, not suppressed withDJANGO_ALLOW_ASYNC_UNSAFE.
Middleware and settings
- Middleware reviewed for async support (
async_capable); third-party middleware upgraded where possible. CONN_MAX_AGEset thoughtfully: async views usingsync_to_asyncshare thread-local connections; persistent connections behave as under WSGI in those threads.SECURE_PROXY_SSL_HEADERand forwarded headers configured for the proxy (--proxy-headers,--forwarded-allow-ipsfor Uvicorn).
Proxy and connections
- Nginx:
proxy_http_version 1.1,UpgradeandConnectionheaders for WebSockets,proxy_buffering offfor streaming and SSE. - Timeouts long enough for streaming and WebSockets on those paths only (
proxy_read_timeout), defaults elsewhere. - Load balancer idle timeouts above WebSocket ping intervals; clients reconnect with backoff.
- Connection limits (
worker_connections, file descriptors) sized for long-lived connections.
Lifespan and shutdown
- Lifespan protocol handled or disabled explicitly (
--lifespan offif the app does not use it, avoiding startup warnings). - Graceful shutdown timeout set; WebSocket clients notified or able to reconnect on deploy.
- Health checks hit a cheap async endpoint; readiness reflects database availability.
Explanation
ASGI servers run Django inside an event loop. Async views yield control while awaiting I/O, so one process can serve many concurrent slow requests. The model breaks down when something blocks the loop: a synchronous network call or ORM query in an async view freezes all requests on that loop until it returns. Django bridges sync and async code with thread pools (sync_to_async), which keeps correctness but adds overhead. Production readiness under ASGI is mostly about keeping async paths truly async and configuring proxies for long-lived connections.
Edge cases / notes
- Channels. WebSocket apps with Channels need a channel layer (Redis) and their own checklist.
- Celery from async views.
task.delay()is synchronous network I/O; call it viasync_to_asyncor use an async-capable client. - Testing. Use
AsyncClientandpytest-asyncioor Django's async test support for async views.
Next steps
- Decide the interface with Django WSGI vs ASGI.
- Write efficient async views with Django Async Views in Production.
- For WebSockets, use the Django Channels Production Checklist.
FAQ
Do sync views work under ASGI?
Yes. Django runs them in a thread pool, but heavy sync workloads gain little from ASGI.
How many ASGI workers should I run?
Typically one process per CPU core, each with its own event loop.
Can I use the Django ORM in async views?
Yes, with async methods like aget and acount, or by wrapping sync code in sync_to_async.
Why is my ASGI app slower than WSGI?
Usually blocking calls in async views or many sync middleware forcing thread switches.