Configuring Uvicorn ASGI behind Nginx Reverse Proxy on Ubuntu 24.04 LTS for Medium Traffic Python FastAPI Apps
Prerequisites and Initial Setup
This guide assumes a fresh Ubuntu 24.04 LTS server instance with root or sudo privileges. We’ll be setting up a production-ready environment for a Python FastAPI application served by Uvicorn, with Nginx acting as a reverse proxy. Ensure you have Python 3.10+ and pip installed. If not, you can install them via:
sudo apt update && sudo apt upgrade -ysudo apt install python3 python3-pip python3-venv -y
Next, create a dedicated user for your application to enhance security. Avoid running web applications as root.
sudo adduser --system --group --no-create-home appuser
Create a directory for your application and set appropriate ownership and permissions.
sudo mkdir -p /opt/my_fastapi_appsudo chown appuser:appuser /opt/my_fastapi_appsudo chmod 755 /opt/my_fastapi_app
FastAPI Application Structure and Uvicorn Configuration
Let’s assume a basic FastAPI application structure. Create a file named main.py within your application directory:
sudo nano /opt/my_fastapi_app/main.py
Populate main.py with a simple FastAPI app:
from fastapi import FastAPI
import uvicorn
app = FastAPI()
@app.get("/")
async def read_root():
return {"message": "Hello from FastAPI!"}
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}
# This part is for direct execution, not for production behind a proxy
# if __name__ == "__main__":
# uvicorn.run(app, host="0.0.0.0", port=8000)
Create a requirements.txt file to list dependencies:
sudo nano /opt/my_fastapi_app/requirements.txt
fastapi uvicorn[standard]
Now, switch to the appuser and set up a Python virtual environment. This isolates dependencies for your application.
sudo su - appusercd /opt/my_fastapi_apppython3 -m venv venvsource venv/bin/activatepip install -r requirements.txt
To run Uvicorn as a service, we’ll use systemd. Create a service file:
sudo nano /etc/systemd/system/my_fastapi_app.service
[Unit] Description=My FastAPI Application After=network.target [Service] User=appuser Group=appuser WorkingDirectory=/opt/my_fastapi_app # Ensure the path to the venv's python executable is correct ExecStart=/opt/my_fastapi_app/venv/bin/uvicorn main:app --host 127.0.0.1 --port 8000 --workers 4 Restart=always RestartSec=10 [Install] WantedBy=multi-user.target
Explanation of the systemd service file:
UserandGroup: Specifies the user and group under which the service will run.WorkingDirectory: Sets the current directory for the service.ExecStart: The command to start the Uvicorn server. We bind it to127.0.0.1(localhost) because Nginx will be the public-facing interface.--workers 4is a starting point; adjust based on your server’s CPU cores (a common recommendation is 2 * num_cores + 1, but for I/O bound apps, fewer might suffice).Restart=always: Ensures the service restarts if it crashes.RestartSec=10: Waits 10 seconds before attempting a restart.
Enable and start the service:
sudo systemctl daemon-reloadsudo systemctl enable my_fastapi_appsudo systemctl start my_fastapi_appsudo systemctl status my_fastapi_app
Verify that Uvicorn is running and accessible locally:
curl http://127.0.0.1:8000
You should see: {"message":"Hello from FastAPI!"}. If not, check the systemd journal for errors: sudo journalctl -u my_fastapi_app -f.
Nginx Installation and Configuration
Install Nginx if it’s not already present:
sudo apt update && sudo apt install nginx -y
Create a new Nginx server block configuration file for your application. Replace your_domain.com with your actual domain name or server IP address.
sudo nano /etc/nginx/sites-available/my_fastapi_app
server {
listen 80;
server_name your_domain.com www.your_domain.com; # Or your server's IP address
location / {
proxy_pass http://127.0.0.1:8000;
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;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Optional: Serve static files directly if your FastAPI app doesn't handle them
# location /static/ {
# alias /opt/my_fastapi_app/static/;
# expires 30d;
# add_header Cache-Control "public";
# }
# Optional: Error pages
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# root /usr/share/nginx/html;
# }
}
Explanation of the Nginx configuration:
listen 80;: Nginx listens on port 80 for incoming HTTP requests.server_name: Specifies the domain names or IP addresses this server block should respond to.location /: This block handles all requests.proxy_pass http://127.0.0.1:8000;: This is the core directive. It forwards all incoming requests to the Uvicorn server running on localhost:8000.proxy_set_headerdirectives: These are crucial for passing information about the original client request to the backend application. Without them, your FastAPI app would see all requests originating from the Nginx server’s IP.proxy_connect_timeout,proxy_send_timeout,proxy_read_timeout: These settings define how long Nginx will wait for a connection to the upstream server, and how long it will wait to send or receive data. Adjust these based on expected response times of your API endpoints.
Enable the new site by creating a symbolic link and test the Nginx configuration:
sudo ln -s /etc/nginx/sites-available/my_fastapi_app /etc/nginx/sites-enabled/sudo nginx -t
If the test is successful, restart Nginx to apply the changes:
sudo systemctl restart nginx
Firewall Configuration (UFW)
If you are using UFW (Uncomplicated Firewall), you need to allow HTTP and HTTPS traffic. Assuming you have UFW enabled:
sudo ufw allow 'Nginx Full'sudo ufw delete allow 'Nginx HTTP'(if you previously allowed only HTTP)sudo ufw enable(if not already enabled)sudo ufw status
This allows traffic on both port 80 (HTTP) and port 443 (HTTPS). For production, you should absolutely configure SSL/TLS with Let’s Encrypt.
SSL/TLS Configuration with Let’s Encrypt (Certbot)
Securing your application with HTTPS is non-negotiable for production environments. We’ll use Certbot to automate this process.
sudo apt install certbot python3-certbot-nginx -y
Run Certbot to obtain and install an SSL certificate. Ensure your DNS records for your_domain.com point to your server’s public IP address before running this.
sudo certbot --nginx -d your_domain.com -d www.your_domain.com
Certbot will prompt you to choose whether to redirect HTTP traffic to HTTPS. It’s highly recommended to choose the redirect option. Certbot automatically modifies your Nginx configuration file to include SSL directives and sets up automatic renewal.
Verify the automatic renewal is set up:
sudo systemctl status certbot.timersudo certbot renew --dry-run
Performance Tuning and Monitoring
For medium traffic, the default settings might suffice, but consider these points for optimization:
- Uvicorn Workers: The
--workers 4in the systemd service is a starting point. Monitor CPU usage. If your application is CPU-bound, you might increase this. If it’s I/O-bound (e.g., heavy database queries, external API calls), fewer workers might be more efficient to avoid context-switching overhead. A common heuristic is(2 * number_of_cores) + 1, but empirical testing is key. - Nginx Worker Processes: Nginx’s
worker_processesdirective (usually set toautoin/etc/nginx/nginx.conf) should typically match the number of CPU cores. - Keep-Alive Connections: Ensure Nginx’s
keepalive_timeoutis set appropriately (e.g.,65s) to reuse TCP connections, reducing latency for subsequent requests from the same client. - Buffering: For large request/response bodies, tune Nginx’s
client_body_buffer_sizeandproxy_buffer_size. However, for typical API traffic, defaults are often fine. - Logging: Configure Nginx and Uvicorn logging levels. For production, avoid verbose debug logging unless actively troubleshooting. Use tools like
goaccessor ELK stack for log analysis. - Monitoring: Implement application performance monitoring (APM) tools (e.g., Sentry, Datadog, New Relic) and server-level monitoring (e.g., Prometheus/Grafana, Zabbix) to track response times, error rates, resource utilization, and identify bottlenecks.
- Database Connections: If your FastAPI app uses a database, ensure you’re using a connection pool (e.g., SQLAlchemy’s pool) and that the pool size is configured appropriately, considering both your application’s needs and database server limits.
Troubleshooting Common Issues
- 502 Bad Gateway: This usually means Nginx cannot reach the Uvicorn process. Check:
- Is the
my_fastapi_app.servicerunning? (sudo systemctl status my_fastapi_app) - Are there errors in the Uvicorn logs? (
sudo journalctl -u my_fastapi_app -f) - Is Uvicorn listening on the correct IP and port specified in Nginx’s
proxy_pass? (sudo ss -tulnp | grep 8000) - Is the firewall blocking traffic between Nginx and Uvicorn (unlikely if both are on localhost)?
- Is the
- 404 Not Found: Ensure your FastAPI routes are correctly defined and that Nginx is proxying to the correct application. If serving static files, verify the
aliasandrootdirectives in Nginx. - Application Errors: Check the Uvicorn logs (
sudo journalctl -u my_fastapi_app -f) for Python tracebacks. Ensure yourrequirements.txtis up-to-date and all dependencies are installed in the virtual environment. - SSL Errors: Verify Certbot’s configuration and renewal status. Ensure your domain’s DNS records are correctly pointing to the server. Check Nginx error logs (
/var/log/nginx/error.log) for SSL-related issues.
This setup provides a robust foundation for deploying medium-traffic FastAPI applications. Remember to adapt worker counts, timeouts, and monitoring strategies based on your specific application’s performance characteristics and traffic patterns.
Leave a Reply
You must be logged in to post a comment.