WSGI and ASGI#

This page covers the choice between WSGI and ASGI, the configuration each requires, and the recommended hosts for each option.

Which to choose#

WSGI is the default and suits most projects. Pick WSGI when every response is synchronous and short lived.

ASGI is the right choice when the project uses any of these features.

  • Server Sent Events for real time updates, including the patch streams described in SSE under WSGI and ASGI.

  • Websockets for bidirectional communication.

  • Async view handlers that await I/O.

  • Streaming responses that hold the connection open.

Both servers run the same next.dj pipeline. The difference lies in how Django dispatches the request.

WSGI configuration#

A standard config/wsgi.py works without modification. See Django’s WSGI deployment guide.

config/wsgi.py#
import os
from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = get_wsgi_application()

A project that split its settings into a package keeps the config.settings.dev default here and exports DJANGO_SETTINGS_MODULE=config.settings.prod for the deployed process. See Split settings per environment.

Run with a production WSGI server such as gunicorn or uwsgi.

shell#
uv run gunicorn config.wsgi:application --workers 4 --bind 0.0.0.0:8000

The example passes --workers 4. Start from a heuristic such as (2 * num_cores) + 1 and tune against the measured concurrency and request duration.

ASGI configuration#

A standard config/asgi.py works without modification. See Django’s ASGI deployment guide.

config/asgi.py#
import os
from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = get_asgi_application()

A project that split its settings into a package keeps the config.settings.dev default here and exports DJANGO_SETTINGS_MODULE=config.settings.prod for the deployed process. See Split settings per environment.

Run with an ASGI server such as daphne or uvicorn.

shell#
uv run uvicorn config.asgi:application --host 0.0.0.0 --port 8000 --workers 4

Worker sizing#

WSGI workers are blocking. One worker handles one request at a time. Size the worker count proportional to the request rate and the average response time.

ASGI workers can multiplex many connections through an event loop. A single worker handles many concurrent SSE or websocket connections. A patch stream (see SSE under WSGI and ASGI) holds its connection open until the source ends or the client disconnects, which outlives any reasonable sync worker timeout. Serve those streams from ASGI workers rather than raising the WSGI worker timeout.

Reverse proxy#

Place a reverse proxy in front of either server for TLS termination and connection handling. The most common choice is nginx or caddy. The next.dj pipeline does not require any special configuration on the proxy beyond standard Django requirements.

Health checks#

Add a health check page that returns 200 quickly. A pure render function avoids the layout chain and the static collector.

notes/pages/healthz/page.py#
from django.http import HttpRequest, JsonResponse

def render(request: HttpRequest) -> JsonResponse:
    return JsonResponse({"status": "ok"})

Configure the load balancer or container orchestrator to hit /healthz/ for liveness.

Concurrency notes#

Form dispatch reuses the request scoped dependency cache so a re-render after validation failure is cheap. This is safe under both WSGI and ASGI because the cache lives on the request, not on the worker process.

The router manager and the components registry are process scoped. Hot reload through router_manager.reload updates the running worker. A multi worker deployment must trigger the reload from every worker or restart the process group.

See also#

See also

Deployment checklist for the deployment checklist. Production settings for the production settings.