Running Python Gunicorn with Uvicorn Workers
python gunicorn with uvicorn workers: Learn how to run FastAPI and other ASGI apps with Gunicorn using Uvicorn workers, including configuration, concurrency, and produ...
When you deploy a FastAPI or Starlette application, you often reach for Gunicorn because it handles process management, signals, and graceful restarts. But Gunicorn is a WSGI server by default, and your ASGI application expects an ASGI server to speak to. The standard solution is to run python gunicorn with uvicorn workers: Gunicorn stays in charge of the process lifecycle, while Uvicorn's worker class provides the ASGI interface and the event loop.
Why Gunicorn Needs Uvicorn Workers
Gunicorn's default worker type is a synchronous WSGI worker. It reads a WSGI environ and calls your application with a callable that returns a response. ASGI applications, on the other hand, are asynchronous and communicate through a different protocol. They expect a server that can handle WebSockets, HTTP/2, and long-lived connections. Uvicorn implements that ASGI layer, but running Uvicorn directly means you manage the process yourself. Combining the two gives you Gunicorn's mature process supervision with Uvicorn's ASGI support.
Setting Up the Worker Class
The simplest way to use Uvicorn workers with Gunicorn is to specify the worker class on the command line:
gunicorn -k uvicorn.workers.UvicornWorker myapp:app
Here myapp:app points to the ASGI application instance. The -k flag selects the worker class. Uvicorn provides two worker classes: UvicornWorker for standard ASGI and UvicornH11Worker for applications that need the h11 protocol implementation instead of httptools. Most applications work fine with the default UvicornWorker.
You can also put this configuration in a Gunicorn config file:
# gunicorn.conf.py worker_class = "uvicorn.workers.UvicornWorker" bind = "0.0.0.0:8000" workers = 4
Then run:
gunicorn -c gunicorn.conf.py myapp:app
Using a config file makes the setup reproducible across environments and keeps command-line flags short.
How Uvicorn Workers Handle Concurrency
Each Gunicorn worker process runs its own Uvicorn server, which in turn runs an event loop. Within that loop, asynchronous tasks are interleaved. This means a single Uvicorn worker can handle many concurrent requests if they are I/O-bound and use await properly. Blocking calls, such as synchronous database drivers or CPU-heavy computations, will block the event loop and stall all requests in that worker.
Gunicorn's workers setting controls the number of processes. The default is 1, which is often too low for production. A common formula is 2 * CPU cores + 1, but the right number depends on your workload. Each worker consumes memory, so more workers also mean higher memory usage. If your application is mostly async and I/O-bound, you may need fewer workers than you would with a synchronous WSGI app.
Configuring Timeouts and Keep-Alive
Gunicorn's default timeout is 30 seconds. With Uvicorn workers, this timeout applies to the entire request handling, including the time spent waiting for the event loop to become free. If your application performs a long-running task, such as a slow external API call, you may hit this limit. Increase it with --timeout or the timeout config option, but be aware that a high timeout can hide performance problems.
The keepalive setting controls how long a connection stays open after a request completes. For HTTP/1.1, a longer keep-alive reduces connection setup overhead. Uvicorn workers support this through Gunicorn's keepalive option, which defaults to 2 seconds. For WebSocket connections, the timeout is handled differently; Gunicorn's timeout does not apply to WebSocket connections because they are managed by the worker's event loop.
Performance Considerations and Worker Count
The main performance trade-off is between process-level parallelism and event-loop concurrency. Uvicorn workers give you both: multiple processes, each with an event loop that can handle many concurrent connections. However, the GIL (Global Interpreter Lock) still limits CPU-bound parallelism within a process. If your application does CPU-heavy work, you need more worker processes to use multiple cores. If it is I/O-bound, a few workers with a well-tuned event loop can handle thousands of connections.
A common mistake is setting workers too high, which leads to memory exhaustion and context-switching overhead. Start with a modest number, measure CPU and memory usage under load, and adjust. Also consider using --worker-class with the UvicornWorker and --threads if you need to run synchronous code in a thread pool. Uvicorn workers support Gunicorn's threads option, which runs a thread pool inside each worker. This can help when you have a mix of async and blocking code.
Common Pitfalls and Troubleshooting
One frequent issue is forgetting that Gunicorn's timeout applies to the whole request. If you see Worker timed out in the logs, the request took longer than the timeout, possibly because the event loop was blocked. Check for synchronous database calls or CPU-bound loops that don't yield control.
Another pitfall is using --reload in production. Gunicorn's reload feature is intended for development and can cause unexpected restarts. Uvicorn workers also have their own reload logic, but it should be disabled in production.
If you need to support WebSockets, ensure you are using UvicornWorker and not a plain WSGI worker. Also, when behind a reverse proxy, set --proxy-protocol or --forwarded-allow-ips correctly so your app sees the client's real IP address. Uvicorn workers respect Gunicorn's --forwarded-allow-ips setting, which is essential for logging and rate limiting.
Alternative Approaches and When to Use Them
Running Uvicorn directly with uvicorn myapp:app --host 0.0.0.0 --port 8000 is simpler and gives you the same ASGI behavior, but you lose Gunicorn's process management. For a single-process deployment, Uvicorn alone is sufficient. For multi-process deployments, you can use uvicorn --workers 4, which uses a similar process model, but Gunicorn offers more mature signal handling and configuration options.
If you need HTTP/2 or other advanced features, consider Hypercorn as an alternative worker class. However, the combination of Gunicorn and Uvicorn is the most widely used setup for FastAPI applications in production, and it is well documented in deployment guides.
Final Configuration Example
A production-ready Gunicorn config for a typical FastAPI app might look like this:
# gunicorn.conf.py import multiprocessing bind = "0.0.0.0:8000" workers = multiprocessing.cpu_count() * 2 + 1 worker_class = "uvicorn.workers.UvicornWorker" timeout = 60 keepalive = 5 forwarded_allow_ips = "*"
This sets the number of workers based on CPU count, uses Uvicorn workers, and adjusts timeouts for slower requests. The forwarded_allow_ips setting is only safe when you control the reverse proxy; otherwise, restrict it to trusted IPs. Adjust these values based on your application's actual behavior and load testing results.