Skip to content
Dashboard

Gunicorn vs Uvicorn: How Python teams pick a production server

Gunicorn serves Python applications through configurable worker classes. It supports the Web Server Gateway Interface (WSGI) and the Asynchronous Server Gateway Interface (ASGI). Uvicorn serves ASGI applications and includes a multiprocess manager. Both can run FastAPI. Choose based on the application interface, concurrency requirements, operating system, and how your platform handles worker health and upgrades.

Copy link to headingWhat are the key differences between Gunicorn and Uvicorn?

Gunicorn separates process supervision from request handling. Its master process, called the arbiter, manages workers, and the selected worker class determines how those workers serve requests. Uvicorn runs ASGI applications on an event loop and can supervise multiple worker processes. Check which restart and upgrade tasks your deployment platform already handles.

Compare protocol support, worker behavior, and platform compatibility before choosing a deployment configuration:

Axis

Gunicorn

Uvicorn

Primary protocol

WSGI by default through the sync worker, with a native ASGI worker

ASGI, with native WSGI mode deprecated in favor of a2wsgi

Concurrency model

Pre-fork arbiter, with the worker class selecting sync, threads, greenlets, or asyncio

One asyncio event loop per process, using uvloop where available

Process supervision

Heartbeat checks plus signal control through HUP, TTIN, TTOU, and USR2

Built-in manager with health-check pings and signal control through HUP, TTIN, and TTOU

HTTP/2 and WebSockets

HTTP/2 in beta on gthread, gevent, and ASGI workers, with WebSockets on the ASGI worker

HTTP/2 experimental through --http zttp --http2, with WebSockets over HTTP/1.1 only

Platform

UNIX only

Linux, macOS, and Windows, with uvloop unavailable on Windows and PyPy

Gunicorn offers several worker models and live binary upgrades. Uvicorn serves ASGI applications and has a multiprocess manager that also works on Windows. Check whether you need Windows support, a particular worker model, or live binary upgrades.

Copy link to headingProtocol support

Gunicorn's default sync worker serves WSGI applications through the callable defined in PEP 3333. Its -k asgi worker serves ASGI applications. ASGI supports HTTP request scopes and WebSocket scopes that last for the connection. Uvicorn serves ASGI directly; its deprecated WSGI mode can be replaced with an adapter such as a2wsgi. Gunicorn's native ASGI worker supports WebSockets directly, while extensions such as Flask-Sock also work with threaded or gevent workers. Uvicorn needs an installed WebSocket implementation, such as the websockets or wsproto package.

Copy link to headingConcurrency model

Gunicorn's worker class determines concurrency. The sync worker handles one request at a time, gthread uses a thread pool, gevent uses greenlets, and the ASGI worker uses an event loop. Uvicorn runs an event loop in each worker and adds processes through --workers. With either ASGI server, handlers must use non-blocking I/O or move blocking calls off the event loop. The application interface and libraries must support the worker class you choose.

Copy link to headingProcess supervision

Gunicorn restarts workers that stop reporting to the arbiter within --timeout. The TTIN and TTOU signals change the worker count, and USR2 starts a new master for a binary upgrade. Uvicorn's multiprocess manager restarts workers that exit unexpectedly or fail its health check. On supported systems, it also resizes the pool with TTIN and TTOU and replaces workers one at a time on SIGHUP. Gunicorn's binary upgrade mechanism matters when the server must coordinate its own upgrade. A container platform can instead replace instances through a rolling deployment.

Copy link to headingPlatform support

Gunicorn uses UNIX process facilities, including fork and fcntl, and does not run natively on Windows. With --preload, it imports the application before forking workers, which can reduce memory use and worker startup time. Uvicorn uses spawn, so workers initialize separately and the multiprocess manager can run on Windows. Windows developers can use Uvicorn locally and in production. To use Gunicorn on Windows, run it through the Windows Subsystem for Linux (WSL) or in a Linux container.

Copy link to headingHow to evaluate Gunicorn for Python deployments

Gunicorn is a pre-fork HTTP server for Python WSGI and ASGI applications. An arbiter process supervises workers, while each worker handles requests using the configured concurrency model.

Gunicorn 25.1.0 promoted the native ASGI worker to stable. The ASGI guide documents serving FastAPI, Starlette, and Quart without Uvicorn. Version 26.0.0 added a framework compatibility suite covering FastAPI, Starlette, Litestar, Quart, Sanic, and BlackSheep. Before switching an existing deployment to the native worker, test the application's behavior and dependencies.

Consider Gunicorn when you need several worker models, worker heartbeat timeouts, or live binary upgrades. These features apply when your team manages the application processes on the host.

Copy link to headingGunicorn pros

  • Proven process supervision: The arbiter restarts crashed workers and replaces workers that stop sending heartbeats. The TTIN and TTOU signals add or remove workers without restarting the master. Use application-level health checks to confirm that requests can complete.

  • Stable native ASGI worker: The -k asgi worker serves FastAPI, Starlette, and other ASGI frameworks with HTTP/1.1 keep-alive, WebSockets, and the lifespan protocol. Uvicorn is not required for this configuration.

  • Zero-downtime binary upgrades: USR2 starts a new Gunicorn master alongside the existing one. For daemonized deployments, WINCH gracefully stops the old workers before the old master retires. This supports upgrades while traffic continues, though requests that exceed graceful_timeout can be interrupted.

  • Concurrency without code changes: Threaded workers can add concurrency to compatible WSGI applications without rewriting route handlers. Gunicorn also offers gevent and ASGI workers.

Copy link to headingGunicorn cons

  • UNIX-only: Gunicorn does not run natively on Windows because it depends on UNIX-specific process facilities. Importing it there can raise ModuleNotFoundError: No module named 'fcntl'. Use WSL, a Linux container, or another server for Windows deployments.

  • Eventlet worker removed: Gunicorn 26.0.0 removed the eventlet worker. Before upgrading a configuration that uses -k eventlet, choose a compatible gevent, gthread, or ASGI worker and test the application's dependencies with it.

  • Container worker-count trap: Gunicorn's configuration example uses multiprocessing.cpu_count() * 2 + 1. In a container, that count can reflect host CPUs rather than the CPU quota. If it reports 64 CPUs, copying the formula configures 129 workers, even with a one-CPU quota. Gunicorn's own default is WEB_CONCURRENCY, or one worker when that variable is unset. Set the worker count explicitly and validate memory use and latency under load.

  • HTTP/2 still in beta: HTTP/2 arrived in 25.0.0 for the gthread, gevent, and ASGI workers. It requires the h2 package (pip install gunicorn[http2]). Teams that need HTTP/2 in production can terminate it at a proxy such as nginx and run Gunicorn on HTTP/1.1 behind it.

Best for: Gunicorn suits WSGI applications on UNIX-like hosts and ASGI deployments that need its worker models or live binary upgrades. Teams often manage these deployments on virtual machines or bare-metal servers.

Pricing: Gunicorn is open source under the MIT license, with no software license fee.

Copy link to headingWhere Gunicorn stands out

Gunicorn's default --timeout is 30 seconds. It measures worker silence, and for non-sync workers, it is not a request-duration limit. A gthread request can run longer while the worker continues reporting to the arbiter. The ASGI guide includes the following settings as a production example. Save them in gunicorn.conf.py, then adjust the worker count and timeouts for the deployment:

gunicorn.conf.py
worker_class = "asgi"
workers = 4
keepalive = 5
timeout = 120 # seconds a silent worker survives before it's killed
graceful_timeout = 30 # seconds workers get to finish requests on shutdown

Gunicorn's signal handling also controls reloads and upgrades. HUP reloads configuration and replaces workers; it reloads application code when --preload is not set. TERM allows requests to finish within graceful_timeout. A binary upgrade starts with USR2; in daemonized deployments, WINCH stops the old workers, and TERM retires the old master after the replacement is ready.

Copy link to headingHow to evaluate Uvicorn for ASGI applications

Uvicorn is an ASGI web server for Python. Each worker runs an event loop that schedules application tasks and handles network connections. When handlers use non-blocking clients, Uvicorn can process other requests while upstream calls wait. The waiting request does not need a dedicated thread or worker process.

Uvicorn had multiprocess support before 0.30.0. That release introduced a new manager with worker monitoring and automatic restarts, and deprecated the uvicorn.workers module. With multiple workers, the manager replaces processes that exit unexpectedly or stop answering health-check pings. It reads the worker count from --workers or WEB_CONCURRENCY, defaulting to one. Uvicorn 0.54.0 requires Python 3.10 or later.

Consider Uvicorn when the application uses ASGI and its built-in manager handles the worker supervision you need. When an orchestrator manages replication and restarts, Uvicorn can run as one server process per container.

Copy link to headingUvicorn pros

  • Native ASGI serving: Uvicorn runs FastAPI, Starlette, and Django's ASGI application directly. FastAPI's fastapi run command also starts Uvicorn, so teams can use the framework's CLI or configure Uvicorn themselves.

  • Windows support: Uvicorn uses spawn, so --workers works on Windows, Linux, and macOS. Signal-based pool resizing and worker reloads are not supported on Windows.

  • Accelerated defaults: Installing uvicorn[standard] adds optional dependencies, including uvloop, httptools, and websockets. Uvicorn selects the available event loop and HTTP parser on supported platforms. Measure their effect on your traffic.

  • Signal-based worker control: On supported systems, TTIN and TTOU add or remove workers. Since 0.51.0, SIGHUP starts each replacement worker before retiring the previous one. Set an appropriate graceful-shutdown timeout for in-flight requests and account for long-lived connections during reloads.

Copy link to headingUvicorn cons

  • Experimental HTTP/2: Version 0.53.0 added HTTP/2 through --http zttp --http2. It requires a separately installed zttp package and remains experimental. Prior-knowledge cleartext HTTP/2 is supported; upgrade-based h2c and WebSockets over HTTP/2 are not.

  • Deprecated WSGI mode: Uvicorn's built-in --interface wsgi mode is deprecated and disables WebSockets. If a WSGI application needs to run through an ASGI server, use an adapter such as a2wsgi and test the resulting behavior.

  • Alpha-status Gunicorn worker: Uvicorn directs Gunicorn users to the separate uvicorn-worker package, which is currently in alpha. Check compatibility and test upgrades.

  • Reduced acceleration on some runtimes: uvloop is unavailable on Windows and PyPy, and httptools does not support PyPy. Windows can use asyncio with httptools; PyPy uses asyncio with the pure-Python h11 parser in the standard configuration. Compare performance on the runtime you plan to deploy.

Best for: Uvicorn suits FastAPI, Starlette, and Django ASGI services, including containers where an orchestrator handles replication. It also supports deployments that need multiple workers on Windows.

Pricing: Uvicorn is open source under the BSD 3-Clause license, with no software license fee.

Copy link to headingWhere Uvicorn stands out

I/O-bound endpoints are where Uvicorn's design pays off. An async handler that awaits a non-blocking HTTP client request or a database query yields control, allowing the event loop to process other requests. A blocking call inside async def blocks the event-loop thread. FastAPI runs plain def path operations in a bounded thread pool, so their blocking I/O stays off that thread:

import time
from fastapi import FastAPI
app = FastAPI()
@app.get("/blocking")
async def blocking():
time.sleep(2) # blocks the event loop, stalling every request on this worker
return {"ok": True}
@app.get("/threaded")
def threaded():
time.sleep(2) # FastAPI runs this in its thread pool, so the loop stays free
return {"ok": True}

The thread pool has finite capacity, so long blocking calls can still queue work. For sustained CPU-intensive work, consider a separate process or task worker. With a multiprocess manager or external supervisor to restart the process, --limit-max-requests can recycle workers after a configured request count. --limit-max-requests-jitter staggers those restarts. Use --reload for development; it cannot be combined with --workers.

Copy link to headingGunicorn vs Uvicorn: which is right for your Python team?

Start with the application's WSGI or ASGI interface. Then choose the worker model and process supervision that fit the deployment environment.

Copy link to headingChoose Gunicorn for synchronous Flask and Django apps

Gunicorn can serve Flask and Django applications through their WSGI entrypoints on UNIX-like hosts. These examples use four workers; load-test your application to choose the count for your deployment:

# Flask app built by an application factory
gunicorn -w 4 'app:create_app()'
# Django project, using the wsgi.py module Django generates
gunicorn -w 4 myproject.wsgi

The default sync worker handles one request at a time and needs a buffering proxy, such as nginx, for production deployments exposed to slow clients. Without that protection, a client that sends or receives data slowly can occupy a worker.

For compatible WSGI workloads, -k gthread --threads 4 adds a thread pool and keep-alive support to each worker. Measure concurrency and memory use before changing the configuration.

Copy link to headingChoose Uvicorn for FastAPI in containers

Uvicorn can serve FastAPI directly in a container and supervise multiple workers without a Gunicorn wrapper. When the deployment does not manage replication externally, set the process count through --workers or WEB_CONCURRENCY. This example starts four workers:

uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4

When using Kubernetes for scaling, FastAPI's container guidance generally recommends one Uvicorn process per container. Multiple workers can still be appropriate for specific resource allocations. Uvicorn's documentation describes the Gunicorn integration and directs users away from the deprecated uvicorn.workers module to uvicorn-worker.

Copy link to headingChoose Gunicorn for ASGI apps on long-lived VMs

Gunicorn is also an option for ASGI applications on long-lived virtual machines or bare-metal hosts. Teams that already use it for WSGI services can keep their deployment tooling and live binary upgrade process. New configurations can use the native ASGI worker; existing Uvicorn-based configurations can use the separate worker package:

# New setup: Gunicorn's native ASGI worker
gunicorn main:app --worker-class asgi --workers 4
# Existing setup: replacement for the deprecated uvicorn.workers class
python -m pip install uvicorn-worker
gunicorn main:app -k uvicorn_worker.UvicornWorker --workers 4

The uvicorn-worker package keeps Uvicorn running under Gunicorn with an updated import path. Its current Alpha classifier warrants a compatibility check and upgrade testing. Gunicorn's native ASGI worker is a separate implementation with beta HTTP/2 support.

Copy link to headingChoose another server for Windows or Django Channels

Other servers may suit your platform or existing stack. Waitress serves WSGI applications on Windows. Daphne was developed for Django Channels, and Granian is a Rust-based server with WSGI and ASGI support. Channels can also run on other compatible ASGI servers. Install the relevant package and export the named application callable before running these commands:

waitress-serve --listen=0.0.0.0:8080 myapp:app
daphne myproject.asgi:application
granian --interface asgi main:app

Copy link to headingHow to deploy Python applications on Vercel

The native Python runtime serves supported WSGI and ASGI applications without a Gunicorn or Uvicorn start command. Use a Docker deployment when the application needs operating-system packages, shared libraries unavailable in the supported Python environment, or a server that requires a custom startup command.

Container deployments still run as Vercel Functions on Fluid compute and follow the platform's execution limits. For self-managed FastAPI, Flask, and Django services, operators choose the server configuration and measure worker capacity.

Copy link to headingSkip server and worker configuration

Vercel's native Python runtime detects supported framework dependencies in requirements.txt, pyproject.toml, or Pipfile and loads the application's app or application entrypoint. For Django, it discovers manage.py and reads WSGI_APPLICATION or ASGI_APPLICATION from the project settings. Each detected app runs as a Vercel Function on Fluid compute. FastAPI lifespan events can initialize resources such as database pools when an instance starts. The FastAPI deployment docs and Flask deployment docs describe the supported layouts and configurations.

For an application in a custom module, set its import path in pyproject.toml:

[tool.vercel]
entrypoint = "my_package.api:app"

Neither Gunicorn nor Uvicorn is required for the native serving path. Keep either package if your local development workflow or another deployment target needs it. The standard Python bundle limit is 500 MB uncompressed. Large functions, in public beta, support eligible bundles up to 5 GB on Fluid compute with Active CPU enabled.

Copy link to headingScale instances while saving on I/O waits

On a self-managed host, worker capacity depends on CPU and memory limits, the work each request performs, and time spent waiting on other services. Too many workers can exhaust memory; too few can limit throughput. Load-test I/O-heavy endpoints, including calls to embedding services, vector databases, and external APIs, with realistic latency and connection limits.

Fluid compute scales instances and lets concurrent invocations share a Python function instance. Vercel uses available capacity before allocating new instances. Active CPU billing pauses during external I/O waits. Provisioned Memory remains billable for instances with requests in flight. Measure both resources when comparing deployment costs.

Copy link to headingCut Python cold-start work

A new Python process imports modules and initializes the application before it can serve requests. If cached bytecode is unavailable, it also compiles Python source during imports. These steps can contribute to startup latency in applications with large dependency trees.

Vercel includes precompiled Python bytecode in Function bundles so the interpreter can skip source compilation at startup. In Vercel's benchmark, cold starts for the median-sized function fell from 2.8 seconds to 1.3 seconds. Automatic concurrency scaling reduces how often new instances start, and pre-warmed instances keep at least one Function instance active for production deployments on paid plans.

Copy link to headingServe WebSockets and streaming without proxy tuning

Self-hosted WebSocket deployments need a compatible application and server configuration. Gunicorn's native ASGI worker supports WebSockets, while Flask-Sock also works with threaded or gevent workers. Uvicorn uses an implementation such as websockets or wsproto. If you use a reverse proxy, configure it to forward upgrade headers and set connection timeouts.

Python Functions on Fluid compute support WebSockets, and a single instance can serve multiple connections. FastAPI uses ASGI WebSocket routes, Django can use Channels, and Flask can use Flask-Sock. Connections close at the Function's maximum duration, so clients need to reconnect and restore their subscriptions or session state. Streaming responses are enabled by default for Python Functions and also follow Function duration limits. For Django applications that broadcast across instances, configure Channels with an external channel layer rather than InMemoryChannelLayer.

Copy link to headingShip your Python app on Vercel without tuning a server

Choose the application server together with the deployment model. Gunicorn offers worker models and live binary upgrades for self-managed hosts. Uvicorn serves ASGI applications directly and can run under its own multiprocess manager or a container orchestrator. Vercel's native Python runtime manages server processes and scaling. Application code still determines request behavior and resource use.

On Vercel, the Python runtime replaces that server work with platform features:

  • Fluid compute scaling: Instances scale with traffic, and optimized concurrency lets invocations share an instance, with Active CPU billed only while code runs.

  • Automatic WSGI and ASGI detection: The runtime finds the framework and the app or application entrypoint and routes requests to it, with no server to install or tune.

  • Precompiled bytecode: Build-time .pyc compilation cut cold starts for a median-sized function from 2.8 seconds to 1.3 seconds in Vercel's benchmarks.

  • WebSockets and streaming: FastAPI, Django, and Flask apps support WebSockets and streaming responses by default on Fluid compute.

  • Docker path for custom servers: Apps that need native dependencies or a specific server can deploy a container and keep full control of the start command.

Start a new project with your FastAPI, Flask, or Django app, or browse templates for a working starting point.

Copy link to headingFrequently asked questions about Gunicorn vs Uvicorn

Copy link to headingIs Gunicorn or Uvicorn better for FastAPI?

Uvicorn is the better default for FastAPI in containers. It serves FastAPI directly, starts through fastapi run. Gunicorn fits long-lived VMs that need binary upgrades, using either its native ASGI worker or the uvicorn-worker package.

Copy link to headingDo I still need Gunicorn in front of Uvicorn workers?

No. Uvicorn can run and supervise multiple workers without Gunicorn. Its 0.30.0 manager added automatic worker restarts, and 0.51.0 added overlapping worker replacement on SIGHUP. A container orchestrator can manage replication and restarts externally. Gunicorn remains an option when you need its worker models or binary upgrade mechanism.

Copy link to headingCan Gunicorn serve ASGI apps without Uvicorn?

Yes. Gunicorn 25.1.0 made its native ASGI worker stable. Run an ASGI application with -k asgi to use HTTP/1.1, WebSockets, and lifespan support; HTTP/2 remains a beta capability. The separate uvicorn-worker package supports deployments that use Uvicorn within Gunicorn.

Copy link to headingDo I need Gunicorn or Uvicorn to deploy Python on Vercel?

No. Vercel's native Python runtime supports WSGI and ASGI applications without requiring either server as a deployment dependency. Keep packages needed for local development or other targets. If you deploy a Docker container, choose the server through its startup command and account for Vercel Function limits.

More Backends articles

Ready to deploy?