WSGI connects synchronous Python applications to web servers through a request/response contract. ASGI uses asynchronous events to support HTTP requests, WebSockets, and application lifecycle management. Its concurrency model suits handlers that overlap non-blocking input/output (I/O) or keep connections open.
Choose based on the protocols your application needs, the libraries its handlers use, and the server configuration you maintain. Moving to ASGI does not inherently make Python computation faster, and both interfaces can serve concurrent requests.
Copy link to headingHow to compare WSGI vs ASGI for your application
The Web Server Gateway Interface (WSGI) and Asynchronous Server Gateway Interface (ASGI) define how a server communicates with application code. The server and framework determine how that contract translates into processes, threads, and concurrent tasks.
WSGI applications can run with synchronous, threaded, or greenlet-based workers. ASGI applications typically run asynchronous tasks on an event loop, with thread pools available for synchronous code.
Copy link to headingWSGI vs ASGI at a glance
Compare protocol support and request execution before deciding whether changing interfaces would address an application requirement.
Copy link to headingTrace the lifetime of a request
WSGI passes the request information in an environ dictionary and provides a start_response callback for status and headers. The application returns an iterable of bytes. The server consumes that iterable to send the response, which can be produced incrementally.
ASGI passes a scope dictionary with request or connection details, plus asynchronous receive and send functions. The application uses these functions to exchange protocol events.
For HTTP, the ASGI invocation covers a single request and its response, even when the underlying connection remains open for subsequent requests. For WebSockets, it covers the lifetime of the connection. A session that exchanges a thousand WebSocket frames can therefore remain within a single invocation.
Copy link to headingStart with the server’s concurrency model
A Gunicorn sync worker handles one request at a time. A gthread worker serves requests through a thread pool, while a gevent worker overlaps compatible I/O through greenlets.
An ASGI worker can advance other tasks when a handler awaits non-blocking I/O. A blocking database call made directly on the event-loop thread prevents those tasks from progressing until it returns.
Running blocking I/O in a thread pool keeps it off the loop, but does not remove capacity limits. Threads, database connections, upstream services, and memory still constrain how many requests the application can handle.
Copy link to headingSeparate streaming from two-way messaging
WSGI can stream an HTTP response through a generator. Server-sent events (SSE) use a long-lived HTTP response to deliver updates from server to client, with event formatting handled by the application or a library.
An open WSGI stream occupies request-handling capacity. With sync workers, that means a process; with threaded workers, a thread. Greenlet-based servers have a different capacity model.
ASGI can send HTTP response chunks while awaiting more data and serving other requests. Its WebSocket protocol also lets an application receive and send messages throughout a connection.
SSE remains one-way under either interface. A chat application can receive updates through SSE and send user messages through separate HTTP requests. WebSockets provide a bidirectional channel over one connection.
Vercel supports streaming Python functions, so streaming alone does not require moving a WSGI application to ASGI.
Copy link to headingLocate startup and shutdown responsibilities
The ASGI lifespan protocol runs initialization and cleanup within the event loop that processes requests. An application can create an asynchronous connection pool at startup and close it during shutdown.
Lifespan runs once per event loop. Separate worker processes, therefore, have separate lifecycle events and resources. Check whether the selected framework implements the lifecycle behavior your application needs.
WSGI defines no application lifespan protocol. Initialization and cleanup depend on framework facilities, server hooks, or process-level setup. Pre-fork deployments also need to account for whether resources are created before or after workers are forked.
Copy link to headingMeasure performance where handlers spend time
Non-blocking I/O lets an ASGI worker handle other requests while external services respond. The benefit depends on how much work can overlap, the framework’s adaptation behavior, and the available connection capacity.
ASGI does not shorten a database query or remove CPU computation. It can reduce waiting within a request when independent operations are explicitly scheduled together.
Profile representative endpoints, then load-test both configurations under the same resource budget. Compare latency distributions, error rates, completed requests, and memory use. Django’s performance guidance supports workload-dependent results, including potential improvements for synchronous applications under ASGI.
Copy link to headingHow to evaluate WSGI for synchronous Python applications
WSGI provides a standard application contract that compatible Python servers can load. Framework routing, authentication, and view handling sit above that contract.
A minimal WSGI application accepts two arguments and returns an iterable of response-body bytes.
def application(environ, start_response): body = b"Hello from WSGI"
start_response( "200 OK", [ ("Content-Type", "text/plain"), ("Content-Length", str(len(body))), ], )
return [body]The returned list contains the entire response body. A generator could produce it over time instead.
Copy link to headingChoose the worker model your dependencies support
Gunicorn offers several ways to serve a WSGI application:
Sync workers: Each process handles one request at a time. An arbiter manages the processes and restarts workers when needed.
Threaded workers: Each process uses a thread pool to overlap requests, including time spent waiting on blocking I/O.
Greenlet workers: Cooperative scheduling overlaps compatible I/O within a process. Some libraries need integration or patching, such as
psycogreenfor Psycopg.
On standard GIL-enabled CPython, threads do not execute Python bytecode in parallel across cores. Native extensions that release the Global Interpreter Lock (GIL) can perform parallel work, and compatible free-threaded builds change that constraint.
Changing worker classes is a server configuration change, but application compatibility still needs testing. The Gunicorn design documentation describes the available models and their tradeoffs.
Copy link to headingBalance WSGI’s strengths against its limits
WSGI fits applications built around synchronous libraries and established deployment tooling.
Its strengths include:
Direct synchronous execution: Ordinary handlers can call blocking libraries without a sync-to-async adaptation layer.
Process-based isolation: Separate workers contain many failures and can be recycled independently.
Established deployment options: Servers support worker management, reverse-proxy integration, profiling, and request logging.
The constraints become more relevant when requests stay open or concurrency increases:
Long-lived responses consume processes, threads, or greenlets according to the server configuration.
WebSockets require facilities outside the WSGI specification.
Asynchronous resource initialization needs framework or server integration rather than a standard lifespan protocol.
Adding worker processes increases memory use, while adding threads or greenlets introduces different compatibility and contention concerns.
Copy link to headingRetain WSGI when it meets your requirements
Admin panels, content sites, and create, read, update, and delete (CRUD) APIs may already meet their latency and capacity requirements under WSGI.
These handlers still wait on queries and connections. Measure the waiting time before concluding that the application has little to gain from another concurrency model.
Copy link to headingRun stacks built on synchronous libraries
A maintained synchronous database driver, authentication library, or software development kit (SDK) can be a reason to retain an existing WSGI deployment.
ASGI can run blocking operations in threads, but the adaptation adds another execution boundary. Compare that cost with the benefit of asynchronous routes elsewhere in the application.
Copy link to headingScale CPU-heavy work separately
Image processing, report generation, and substantial serialization need CPU capacity. Switching interfaces does not reduce that computation.
Processes can provide parallel execution on standard CPython. Work that exceeds an interactive request’s resource or latency budget may belong in a separate worker. See backend workload types and execution models for that broader decision.
Copy link to headingHow to evaluate ASGI for asynchronous and realtime applications
ASGI defines an event-driven interface for HTTP, WebSockets, and application lifecycle events. FastAPI, Starlette, Quart, and Litestar use it natively. Django supports both ASGI and WSGI.
A minimal ASGI application receives the protocol scope and asynchronous event functions:
async def application(scope, receive, send): if scope["type"] != "http": raise NotImplementedError( f"Unsupported scope type: {scope['type']}" )
await send( { "type": "http.response.start", "status": 200, "headers": [(b"content-type", b"text/plain")], } )
await send( { "type": "http.response.body", "body": b"Hello from ASGI", } )This example handles HTTP only. Frameworks provide routing, response objects, and protocol dispatch above the interface.
Copy link to headingLearn the events your application handles
The scope["type"] value identifies the protocol. Each event also has a type field:
HTTP requests:
http.requestevents carry request-body chunks. The application sendshttp.response.startand one or morehttp.response.bodyevents.WebSocket connections: The application receives
websocket.connect, accepts withwebsocket.accept, and exchanges message events until disconnection.Application lifespan: Startup and shutdown events let the application initialize and release resources in the serving event loop.
The HTTP and WebSocket specification defines the message formats and invocation lifetimes.
Copy link to headingKeep blocking work off the event-loop thread
An async def declaration does not make every operation inside a handler non-blocking. Database drivers, HTTP clients, file access, and other dependencies need to be reviewed.
Use async clients for non-blocking I/O. Move blocking operations to a synchronous handler or an explicit thread-pool call. Ordinary synchronous helpers can run directly when they finish promptly.
Framework behavior matters here. FastAPI runs plain def path operations and dependencies in a thread pool, but does not automatically offload utility functions called directly from an async handler. Substantial CPU work also needs separate handling.
Copy link to headingBalance ASGI’s capabilities against its constraints
ASGI provides a standard interface for WebSockets and lifecycle events. Non-blocking handlers can also share event-loop capacity while waiting.
The constraints follow from cooperative execution:
Blocking work on the loop delays other tasks.
Sync views, middleware, and dependencies may require adaptation.
Connection pools and upstream services can limit useful concurrency before the event loop does.
Failure analysis needs to account for tasks affecting one another through shared loop capacity.
ASGI is most useful when these responsibilities match the application’s workload and the team’s operating model.
Copy link to headingStream model output while serving other requests
An application streaming output from a large language model (LLM) often waits between chunks from the provider. An async client lets the handler await those chunks while other requests progress.
Format the HTTP stream correctly, handle disconnections, and check the host’s duration limits. The interface alone does not supply SSE framing or application recovery behavior.
Copy link to headingSchedule independent upstream calls together
When a single request requires independent results from multiple databases or APIs, schedule those operations concurrently via async clients.
asyncio.TaskGroup or asyncio.gather can overlap the waits. Sequential await statements do not automatically run operations together, and calls that depend on earlier results must preserve that order.
Copy link to headingHold bidirectional sessions open
WebSockets suit collaborative editors, chat, and other features that exchange messages throughout a session. ASGI defines this behavior directly. Django projects can add WebSocket consumers and routing through Channels.
Clients still need reconnect logic. Applications serving multiple instances also need a way to coordinate messages and shared state across those instances.
Copy link to headingHow to choose WSGI or ASGI for an existing stack
Start with a representative endpoint and its dependencies. Then establish whether protocol requirements or measured resource use justify changing the request-handling model.
If the framework choice is still open, FastAPI vs Flask compares the API features and dependencies above these interfaces.
Copy link to headingCheck Flask and Django before migrating
Flask async views under a conventional WSGI server use a request-scoped event loop. Calls within the view can overlap, but the request still occupies serving capacity. An ASGI adapter changes the loop lifecycle without making Flask an async-first framework.
In Django, synchronous middleware between an ASGI server and an async view introduces adaptation and an open thread for exception propagation. This adaptation can limit concurrency for I/O-bound requests. Database transactions still belong in a synchronous function called through sync_to_async.
Single-client tests can expose compatibility problems and adaptation costs. Concurrent tests are needed to assess queueing, contention, and long-lived connections.
Copy link to headingCombine both interfaces with a hybrid or adapter pattern
A reverse proxy can route realtime paths such as /ws/ to an ASGI backend while WSGI continues serving existing routes.
Adapters provide another option. asgiref.wsgi.WsgiToAsgi runs a WSGI app in a thread behind an ASGI interface and rejects non-HTTP scopes. a2wsgi.WSGIMiddleware uses a configurable thread pool, with up to 10 threads by default.
An ASGI application can mount an adapted WSGI app under one path and handle other routes itself. The WSGI portion retains the adapter’s limitations.
Running both adds routing, libraries, and deployment boundaries to maintain. Compare that work with the measured benefit of changing the existing application.
Copy link to headingEvaluate hosting requirements separately
The interface does not determine whether the application fits a host’s resources, dependency support, or request-duration limits.
Check those requirements alongside database access and operational needs. Where should you host a Python API? covers the deployment decision beyond WSGI and ASGI.
Copy link to headingHow to run WSGI and ASGI applications on Vercel
Self-hosting requires a server and worker configuration compatible with the application. On Vercel, both interfaces use the Python runtime, with Vercel managing the serving infrastructure.
The platform facilities are shared. Application code, framework behavior, and external connection limits still determine observed performance.
Copy link to headingDeploy a supported Python entrypoint
Vercel detects supported frameworks from dependencies in requirements.txt, pyproject.toml, or Pipfile with its corresponding lockfile. Define a top-level app or application at a supported entry point so the runtime can load it.
Framework-specific instructions cover Flask and Django. The guide to shipping a FastAPI app on Vercel walks through deployment from a repository, template, or the Vercel CLI.
For Django projects defining both ASGI_APPLICATION and WSGI_APPLICATION, Vercel selects the ASGI entrypoint.
Copy link to headingShare instance capacity during I/O waits
Fluid compute allows multiple Python invocations to share one function instance. Overlapping I/O-bound requests can improve resource use and reduce the number of instances needed.
ASGI handlers cooperate by awaiting non-blocking operations. WSGI applications can also use the runtime’s concurrency facilities, with useful capacity depending on their request handling and dependencies.
CPU-heavy requests can compete for the same resources. Measure latency and instance use under representative concurrent traffic.
Copy link to headingSeparate CPU execution from elapsed duration
A request that spends 100 milliseconds processing data and 400 milliseconds waiting for a query has 500 milliseconds of elapsed duration. With Active CPU billing, that example consumes 100 milliseconds of active CPU.
Both interfaces use this billing model. Sharing instances can reduce memory cost per request, but neither interface guarantees a lower bill.
Copy link to headingServe Python WebSockets without a separate socket service
WebSocket support in Vercel Functions is generally available. Python integrations include FastAPI with ASGI, Django Channels with an ASGI entrypoint, and Flask-Sock with WSGI.
Enable Fluid compute before serving WebSocket connections on Vercel Functions.
These framework integrations use their existing WebSocket facilities without a Vercel-specific upgrade API.
Each connection stays pinned to one function instance and closes at the function’s maximum duration. Design clients to reconnect and restore their sessions. Use shared storage or messaging when connections on different instances need to coordinate.
Copy link to headingReduce startup compilation and stream responses
Vercel includes precompiled Python bytecode for application code and dependencies when bundle space allows.
In Vercel’s benchmark, cold starts for a median-sized function fell from 2.8 seconds to 1.3 seconds. Results vary with the application and space available for bytecode. No application code changes are required.
Both WSGI and ASGI applications can use this optimization and Python response streaming. Test the deployed application’s initialization, stream behavior, and connection lifetime.
Once the entrypoint and dependencies are ready, import a project or choose a Vercel template.
Copy link to headingFrequently asked questions about WSGI vs ASGI
Copy link to headingCan I run a WSGI application on an ASGI server?
Yes. An adapter such as asgiref.WsgiToAsgi or a2wsgi.WSGIMiddleware exposes the application through an ASGI interface.
The WSGI code still runs synchronously through the adapter. Wrapping it does not add native WebSocket handling. An outer ASGI application can manage its own lifecycle and other protocol routes.
Copy link to headingCan I run an ASGI application on a WSGI server?
Yes, with limitations. a2wsgi.ASGIMiddleware adapts an ASGI application to WSGI, but the WSGI boundary does not carry native WebSocket or lifespan events.
The adapter waits for the ASGI application’s background tasks before finishing the response, with a configurable wait limit. Check the a2wsgi documentation against the application’s requirements.
Copy link to headingIs FastAPI automatically faster because it uses ASGI?
No. Framework features, application code, dependencies, and server configuration all affect performance.
FastAPI sends plain def handlers to a thread pool. A trivial non-blocking handler can avoid that handoff with async def, but substantial CPU work should still stay off the event-loop thread.
Copy link to headingHow should Django database connections work under ASGI?
Disable persistent connections configured through CONN_MAX_AGE in async mode. Use the database backend’s built-in pooling where available, or evaluate another pooling option.
Size the pool based on expected concurrent queries and the number of application instances. Django’s async database documentation covers connection handling and transaction restrictions.
Copy link to headingDoes free-threaded Python change the WSGI vs ASGI choice?
It changes the CPU parallelism available to threads, rather than the protocols an interface supports.
Python 3.14 made the optional free-threaded build officially supported. Compatible applications can execute Python code across cores, although some extensions can re-enable the GIL. See Python’s free-threading documentation.
ASGI still defines WebSocket and lifespan protocols. WSGI applications still need compatible extensions for WebSockets and framework or server facilities for initialization and cleanup.