Installing and Tuning Uvicorn + Gunicorn process trees on Ubuntu 24.04 LTS for high-traffic FastAPI Backends
System Preparation and Initial Setup
This guide focuses on deploying and optimizing FastAPI applications using Uvicorn and Gunicorn on Ubuntu 24.04 LTS. We’ll cover the installation, configuration, and advanced tuning required for high-traffic scenarios. Ensure your system is up-to-date before proceeding.
Begin by updating your package lists and upgrading existing packages:
sudo apt update && sudo apt upgrade -y
Next, install Python 3, pip, and the `venv` module, which is crucial for isolated Python environments. Ubuntu 24.04 typically ships with Python 3.12, which is well-suited for modern FastAPI development.
sudo apt install python3 python3-pip python3-venv -y
Creating a Virtual Environment and Installing Dependencies
It’s best practice to isolate your application’s dependencies. Create a dedicated directory for your FastAPI application and set up a Python virtual environment.
mkdir ~/my_fastapi_app cd ~/my_fastapi_app python3 -m venv venv source venv/bin/activate
With the virtual environment activated, install FastAPI, Uvicorn (as an ASGI server), and Gunicorn (as a process manager). We’ll also install `uvicorn[standard]` for enhanced performance features.
pip install fastapi uvicorn[standard] gunicorn
Create a simple FastAPI application for testing purposes. Save this as main.py within your application directory.
from fastapi import FastAPI
import time
app = FastAPI()
@app.get("/")
async def read_root():
# Simulate some work
time.sleep(0.1)
return {"Hello": "World"}
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}
Running with Uvicorn Directly (for Development/Testing)
Before moving to Gunicorn, verify your application runs correctly with Uvicorn. This command starts Uvicorn with 4 worker processes, suitable for initial testing.
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
You can test this locally by navigating to http://localhost:8000 in your browser or using curl.
curl http://localhost:8000
Configuring Gunicorn with Uvicorn Workers
Gunicorn will act as the master process, spawning and managing Uvicorn worker processes. This provides better process management, signal handling, and stability under load.
Create a Gunicorn configuration file, typically named gunicorn_config.py, in your application’s root directory. This file allows for fine-grained control over Gunicorn’s behavior.
import multiprocessing # Bind to a specific IP and port bind = "0.0.0.0:8000" # Number of worker processes. A common starting point is (2 * number_of_cores) + 1. # We'll use a dynamic approach here. workers = multiprocessing.cpu_count() * 2 + 1 # Use Uvicorn workers # The 'worker_class' should be set to 'uvicorn.workers.UvicornWorker' worker_class = "uvicorn.workers.UvicornWorker" # Maximum number of requests a worker can handle before restarting. # This helps prevent memory leaks. max_requests = 1000 # Timeout in seconds for worker processes. # Adjust based on your application's typical request latency. timeout = 30 # Logging configuration loglevel = "info" accesslog = "-" # Log to stdout errorlog = "-" # Log to stderr # Optional: Threads per worker (if using Uvicorn's asyncio workers) # threads = 2 # This is more relevant for Uvicorn's default workers, but can be set.
Now, run your application using Gunicorn, pointing it to your FastAPI app instance and specifying the configuration file.
gunicorn -c gunicorn_config.py main:app
Setting up Systemd for Production Deployment
For production, you need a robust way to manage your Gunicorn process. Systemd is the standard init system on Ubuntu and is ideal for this. Create a systemd service file.
First, create a dedicated user for your application to run under, enhancing security.
sudo useradd -r -m -s /bin/false myfastapiuser
Now, create the systemd service file. Replace /path/to/your/app with the actual path to your application directory and myfastapiuser with the user you just created.
[Unit]
Description=Gunicorn instance to serve my FastAPI application
After=network.target
[Service]
User=myfastapiuser
Group=myfastapiuser
WorkingDirectory=/path/to/your/app
ExecStart=/path/to/your/app/venv/bin/gunicorn \
--workers 4 \
--bind unix:/run/myfastapi.sock \
main:app
# Optional: If you prefer to use the gunicorn_config.py file
# ExecStart=/path/to/your/app/venv/bin/gunicorn \
# -c /path/to/your/app/gunicorn_config.py \
# main:app
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Save this file as /etc/systemd/system/myfastapi.service. Ensure the path to gunicorn points to the executable within your virtual environment.
Reload the systemd daemon, start the service, and enable it to start on boot.
sudo systemctl daemon-reload sudo systemctl start myfastapi sudo systemctl enable myfastapi
Check the status of your service:
sudo systemctl status myfastapi
Integrating with Nginx as a Reverse Proxy
For production, Nginx is an excellent choice for a reverse proxy, handling SSL termination, load balancing, and serving static files efficiently. We’ll configure Nginx to proxy requests to the Gunicorn socket.
Install Nginx if you haven’t already:
sudo apt install nginx -y
Create an Nginx server block configuration file for your application. Replace your_domain.com with your actual domain name.
server {
listen 80;
server_name your_domain.com www.your_domain.com;
location / {
proxy_pass http://unix:/run/myfastapi.sock;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Optional: Serve static files directly if your FastAPI app doesn't handle them
# location /static/ {
# alias /path/to/your/app/static/;
# }
# Optional: Add SSL configuration here for HTTPS
# listen 443 ssl;
# ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
# include /etc/letsencrypt/options-ssl-nginx.conf;
# ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
Save this file as /etc/nginx/sites-available/myfastapi. Then, create a symbolic link to enable the site and test the Nginx configuration.
sudo ln -s /etc/nginx/sites-available/myfastapi /etc/nginx/sites-enabled/ sudo nginx -t
If the test is successful, reload Nginx.
sudo systemctl reload nginx
Performance Tuning and Advanced Considerations
Tuning involves several key areas:
- Worker Processes (Gunicorn): The
workers = multiprocessing.cpu_count() * 2 + 1formula is a good starting point for I/O-bound applications. For CPU-bound tasks, you might reduce this tomultiprocessing.cpu_count(). Monitor CPU and memory usage to find the optimal number. - Worker Class (Uvicorn): Ensure you are using
uvicorn.workers.UvicornWorker. Uvicorn’s workers are asynchronous and generally perform better than Gunicorn’s default synchronous workers for I/O-bound workloads common in web APIs. - Max Requests: Setting
max_requests(e.g., 1000) helps mitigate memory leaks by periodically restarting workers. Adjust this value based on your application’s memory footprint over time. - Timeouts: The
timeoutsetting in Gunicorn prevents workers from hanging indefinitely. For long-running operations, consider using background task queues (like Celery or RQ) instead of blocking the web server. - Keep-Alive Connections: Nginx’s default keep-alive settings are usually sufficient. Ensure your Nginx configuration is optimized for connection handling.
- Asynchronous Code: Write your FastAPI application using
async/awaitextensively. Blocking I/O operations within async functions will negate the benefits of Uvicorn and FastAPI. Use libraries that support async operations (e.g.,httpxfor HTTP requests,asyncpgfor PostgreSQL). - Database Connections: Use an asynchronous database driver (e.g.,
asyncpgfor PostgreSQL,aiomysqlfor MySQL) and manage connection pools efficiently. - Caching: Implement caching strategies (e.g., Redis) for frequently accessed data to reduce database load and response times.
- Load Testing: Use tools like
locustork6to simulate high traffic and identify bottlenecks before deploying to production.
For very high-traffic scenarios, consider a multi-process, multi-worker setup with Nginx acting as a load balancer across multiple Gunicorn instances, potentially on different servers.
Monitoring and Logging
Effective monitoring and logging are critical for identifying and resolving issues in production.
- Systemd Journal: Gunicorn logs to stdout/stderr by default when configured via systemd. You can access these logs using
journalctl.
sudo journalctl -u myfastapi -f
- Nginx Logs: Monitor Nginx access and error logs for issues related to request handling and proxying.
sudo tail -f /var/log/nginx/access.log sudo tail -f /var/log/nginx/error.log
- Application-Level Logging: Implement structured logging within your FastAPI application using libraries like
structlogor Python’s built-inloggingmodule, configured to output JSON for easier parsing by log aggregation tools (e.g., ELK stack, Splunk). - Performance Monitoring: Integrate Application Performance Monitoring (APM) tools like Datadog, New Relic, or Sentry to gain deep insights into request latency, error rates, and resource utilization.
By following these steps, you can establish a robust, scalable, and performant deployment of your FastAPI application on Ubuntu 24.04 LTS, ready to handle significant traffic loads.
Leave a Reply
You must be logged in to post a comment.