WSGI and ASGI are interfaces between a Python web application and the server accepting connections. WSGI targets synchronous HTTP request and response. ASGI supports that model plus asynchronous, long-lived connections such as WebSockets and streaming. The short answer: keep WSGI for mature synchronous applications without real-time requirements; choose ASGI when the framework, libraries, and workload benefit from asynchronous concurrency or persistent protocols.

Neither is a framework or a server. Django, Flask, and FastAPI are frameworks; Gunicorn, uWSGI, Uvicorn, Daphne, and Hypercorn serve applications. WSGI or ASGI defines the contract joining those layers. PEP 3333 specifies WSGI, while the official ASGI specification defines the asynchronous protocol.

How WSGI works

A WSGI server calls a Python object with environ and start_response. The application returns an iterable of bytes. Each call represents one HTTP request and occupies its worker while code runs or waits for I/O. This compact interface created a stable ecosystem that remains easy to reason about and suitable for many websites.

def application(environ, start_response):
    body = b"Hello, WSGI!"
    start_response("200 OK", [
        ("Content-Type", "text/plain; charset=utf-8"),
        ("Content-Length", str(len(body))),
    ])
    return [body]

WSGI concurrency generally comes from multiple processes or threads. If a database call is slow, that worker waits while others can handle traffic. This does not make WSGI inherently slow. For CRUD, templates, and database-heavy applications, query plans, caching, and sound code often matter more than changing the interface. Our Python web development guide introduces the wider ecosystem.

How ASGI works

An ASGI server calls an asynchronous application with scope, receive, and send. The scope describes a connection, while events arrive and leave through awaitable callables. This model represents HTTP, WebSocket, and lifespan events without holding one blocked thread for every idle connection.

async def application(scope, receive, send):
    assert scope["type"] == "http"
    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!",
    })

When a coroutine awaits nonblocking network I/O, the event loop can advance other connections. That model benefits many simultaneous waits, streaming, and real-time traffic. It does not accelerate CPU work or turn synchronous libraries into asynchronous ones. Read Python async and await for the underlying execution model.

Architectural differences

  • Protocols: WSGI models HTTP; ASGI models HTTP, WebSocket, and application lifespan.
  • Execution: WSGI invokes synchronous code; ASGI permits async code and can adapt synchronous handlers.
  • Long connections: limited streaming is possible in WSGI, but WebSocket is outside its contract; ASGI represents both directly.
  • Concurrency: WSGI scales through processes and threads; ASGI also uses processes while multiplexing I/O per event loop.
  • Complexity: WSGI has fewer sync/async boundaries; ASGI requires attention to blocking, cancellation, and shared resources.

ASGI does not remove the need for workers. A process is still constrained by CPU, memory, and its event loop. Multiple processes provide parallelism and fault isolation. ASGI also does not guarantee higher throughput. Benchmark representative database and external-service activity rather than a “hello world” route.

Choose by use case

Prefer WSGI when the application is fully synchronous, relies on blocking libraries, has no WebSocket requirement, and already meets its objectives. A traditional Django site or Flask API with ordinary queries can remain on WSGI without creating technical debt. See the Flask REST API guide for that architecture.

Prefer ASGI for WebSockets, server-sent events, streaming, long polling, or many concurrent network calls made through async clients. FastAPI and Starlette are ASGI-native; our FastAPI guide demonstrates that model. For bidirectional traffic, read Python WebSockets.

Neither interface is a durable job queue. Email delivery, video conversion, and large report generation should run in a task system if they must survive request completion and restarts. Scheduling a coroutine in the ASGI process can lose work during deployment and compete with web traffic.

Django under WSGI and ASGI

Django provides both wsgi.py and asgi.py. ASGI deployment enables async views and asynchronous connections when the rest of the stack supports them. Synchronous middleware may trigger adaptation and reduce the gain. Django documents supported WSGI deployment and ASGI deployment separately.

Before migrating, inventory middleware, database drivers, cache clients, authentication, and HTTP clients. Use async-capable versions where needed and keep blocking calls off the event loop. Django can bridge execution styles, but frequent switches add overhead and make behavior harder to reason about. Our complete Django guide covers framework structure.

Servers and production deployment

Gunicorn is a common WSGI choice on Unix-like systems, often behind a reverse proxy. Uvicorn, Daphne, and Hypercorn implement ASGI. Gunicorn can also supervise compatible ASGI workers, subject to current server documentation. On Windows, use a server that explicitly supports the target environment rather than assuming Unix process behavior.

The proxy typically terminates TLS, applies body limits and timeouts, and forwards trusted metadata. Configure client IP and scheme handling deliberately; never trust forwarded headers from arbitrary clients. Add graceful shutdown, health checks, connection limits, and observability. For WebSockets, verify upgrade support and idle timeouts across the proxy, load balancer, and application server.

Common mistakes

  • Moving to ASGI and expecting CPU-heavy code or slow SQL to become faster.
  • Calling requests, synchronous drivers, or expensive functions directly in an async view.
  • Running a development server in production.
  • Assuming one event loop eliminates process workers and concurrency limits.
  • Migrating without auditing middleware and sync/async boundaries.
  • Using in-process tasks for work that requires reliable delivery.

Measure percentile latency, throughput, errors, CPU, open connections, and database saturation. Test disconnects and cancellation for streaming responses. An ASGI application that blocks its event loop can perform worse than a properly sized WSGI deployment.

Frequently asked questions

Is ASGI always faster than WSGI?

No. ASGI can handle many concurrent waits efficiently, but the database, code, server configuration, and workload determine performance. CPU work still needs processes or separate jobs.

Can a WSGI application run on an ASGI server?

Adapters exist, but they do not make application code asynchronous and can add overhead. Prefer the framework's native interface whenever practical.

Does Django require ASGI for async views?

Async views can be adapted under WSGI, but an ASGI stack better preserves the benefits of async connections and long-lived protocol features.

Which interface should a new project use?

Follow the framework's primary interface and product requirements. FastAPI points to ASGI; a synchronous Django project can choose either without migrating merely for fashion.

Conclusion

WSGI remains a strong solution for synchronous HTTP applications. ASGI expands the contract to asynchronous concurrency, streaming, and WebSockets, but requires a nonblocking stack and careful operation. Choose based on protocols and waiting behavior, audit libraries, deploy an appropriate production server, and measure realistic traffic. The right option is the simplest one that meets requirements without hiding bottlenecks.