Deploy production-ready FastAPI applications using Uvicorn ASGI server with systemd process management and Nginx reverse proxy. Configure SSL termination, load balancing, and comprehensive monitoring for high-performance Python web applications.
Prerequisites
- Root or sudo access
- Basic knowledge of Python and web servers
- Domain name for SSL setup (optional)
What this solves
Uvicorn is a lightning-fast ASGI server that powers FastAPI applications in production environments. This tutorial shows you how to deploy Uvicorn with systemd for automatic process management, Nginx reverse proxy for SSL termination and load balancing, and comprehensive monitoring with health checks. You'll learn to configure multiple Uvicorn workers, optimize performance settings, and implement security best practices for production deployments.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest versions of all dependencies.
sudo apt update && sudo apt upgrade -y
Install Python and dependencies
Install Python 3.11+ and essential development tools needed for building Python packages and managing virtual environments.
sudo apt install -y python3.11 python3.11-venv python3.11-dev python3-pip build-essential nginx
Create application user and directory
Create a dedicated system user for running your FastAPI application. This follows security best practices by avoiding root privileges.
sudo useradd --system --shell /bin/false --home-dir /opt/fastapi fastapi
sudo mkdir -p /opt/fastapi
sudo chown fastapi:fastapi /opt/fastapi
Create sample FastAPI application
Create a simple FastAPI application for testing your Uvicorn deployment. This includes health check endpoints for monitoring.
sudo -u fastapi python3.11 -m venv /opt/fastapi/venv
sudo -u fastapi /opt/fastapi/venv/bin/pip install fastapi uvicorn[standard] python-multipart
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
import uvicorn
import os
import psutil
from datetime import datetime
app = FastAPI(title="FastAPI with Uvicorn", version="1.0.0")
@app.get("/")
async def root():
return {"message": "FastAPI with Uvicorn is running", "timestamp": datetime.now().isoformat()}
@app.get("/health")
async def health_check():
return {
"status": "healthy",
"timestamp": datetime.now().isoformat(),
"version": "1.0.0"
}
@app.get("/metrics")
async def metrics():
process = psutil.Process()
return {
"memory_usage": process.memory_info().rss / 1024 / 1024, # MB
"cpu_percent": process.cpu_percent(),
"connections": len(process.connections()),
"uptime": datetime.now().isoformat()
}
@app.get("/api/users/{user_id}")
async def get_user(user_id: int):
if user_id < 1:
raise HTTPException(status_code=400, detail="Invalid user ID")
return {"user_id": user_id, "username": f"user{user_id}", "active": True}
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=8000)
sudo chown fastapi:fastapi /opt/fastapi/app.py
sudo -u fastapi /opt/fastapi/venv/bin/pip install psutil
Create Uvicorn configuration file
Configure Uvicorn with production-optimized settings including multiple workers, proper logging, and performance tuning.
[application]
app = "app:app"
host = "127.0.0.1"
port = 8000
workers = 4
worker-class = "uvicorn.workers.UvicornWorker"
[logging]
level = "info"
access-log = true
use-colors = false
[performance]
max-requests = 1000
max-requests-jitter = 100
timeout = 30
keepalive = 5
[ssl]
ssl-keyfile = null
ssl-certfile = null
sudo chown fastapi:fastapi /opt/fastapi/uvicorn.toml
Create systemd service file
Configure systemd to manage Uvicorn processes with automatic restart, resource limits, and security hardening.
[Unit]
Description=FastAPI application with Uvicorn
After=network.target
Wants=network.target
[Service]
Type=exec
User=fastapi
Group=fastapi
WorkingDirectory=/opt/fastapi
Environment=PATH=/opt/fastapi/venv/bin
Environment=PYTHONPATH=/opt/fastapi
Environment=PYTHONUNBUFFERED=1
ExecStart=/opt/fastapi/venv/bin/uvicorn app:app --host 127.0.0.1 --port 8000 --workers 4 --log-level info --access-log
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=5
TimeoutStopSec=30
Security settings
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/opt/fastapi
SystemCallArchitectures=native
Resource limits
LimitNOFILE=65535
LimitNPROC=4096
[Install]
WantedBy=multi-user.target
Create log directory and enable service
Set up proper logging directory with correct permissions and enable the systemd service for automatic startup.
sudo mkdir -p /var/log/fastapi
sudo chown fastapi:fastapi /var/log/fastapi
sudo chmod 755 /var/log/fastapi
sudo systemctl daemon-reload
sudo systemctl enable fastapi
sudo systemctl start fastapi
Configure Nginx reverse proxy
Set up Nginx as a reverse proxy with load balancing, security headers, and SSL termination support.
upstream fastapi_backend {
server 127.0.0.1:8000;
keepalive 32;
}
server {
listen 80;
server_name example.com www.example.com;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:" always;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=health:10m rate=100r/s;
location / {
limit_req zone=api burst=20 nodelay;
proxy_pass http://fastapi_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
}
location /health {
limit_req zone=health burst=50 nodelay;
proxy_pass http://fastapi_backend;
proxy_http_version 1.1;
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;
access_log off;
}
location /metrics {
limit_req zone=api burst=10 nodelay;
# Restrict metrics access
allow 127.0.0.1;
allow 10.0.0.0/8;
allow 172.16.0.0/12;
allow 192.168.0.0/16;
deny all;
proxy_pass http://fastapi_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# Static files (if needed)
location /static/ {
alias /opt/fastapi/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# Logging
access_log /var/log/nginx/fastapi_access.log;
error_log /var/log/nginx/fastapi_error.log;
}
sudo ln -s /etc/nginx/sites-available/fastapi /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
Configure SSL with Let's Encrypt (optional)
Set up automatic SSL certificate management using Certbot for production deployments.
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
Configure log rotation
Set up automatic log rotation to prevent disk space issues in production environments.
/var/log/fastapi/*.log {
daily
missingok
rotate 52
compress
delaycompress
notifempty
copytruncate
su fastapi fastapi
}
Create monitoring script
Implement health checking and monitoring script for automated alerting and service recovery.
#!/usr/bin/env python3
import requests
import subprocess
import sys
import json
from datetime import datetime
def check_health():
try:
response = requests.get('http://127.0.0.1:8000/health', timeout=5)
if response.status_code == 200:
data = response.json()
print(f"Health check passed: {data['status']} at {data['timestamp']}")
return True
else:
print(f"Health check failed: HTTP {response.status_code}")
return False
except requests.exceptions.RequestException as e:
print(f"Health check failed: {e}")
return False
def check_metrics():
try:
response = requests.get('http://127.0.0.1:8000/metrics', timeout=5)
if response.status_code == 200:
data = response.json()
print(f"Memory usage: {data['memory_usage']:.1f}MB")
print(f"CPU usage: {data['cpu_percent']:.1f}%")
print(f"Connections: {data['connections']}")
return True
else:
print(f"Metrics check failed: HTTP {response.status_code}")
return False
except requests.exceptions.RequestException as e:
print(f"Metrics check failed: {e}")
return False
def restart_service():
try:
subprocess.run(['sudo', 'systemctl', 'restart', 'fastapi'], check=True)
print("Service restarted successfully")
return True
except subprocess.CalledProcessError as e:
print(f"Failed to restart service: {e}")
return False
if __name__ == "__main__":
print(f"FastAPI monitoring check - {datetime.now().isoformat()}")
if not check_health():
print("Health check failed, attempting service restart...")
if restart_service():
# Wait and check again
import time
time.sleep(10)
if check_health():
print("Service recovered successfully")
sys.exit(0)
else:
print("Service recovery failed")
sys.exit(1)
else:
sys.exit(1)
check_metrics()
print("All checks passed")
sudo chown fastapi:fastapi /opt/fastapi/monitor.py
sudo chmod 755 /opt/fastapi/monitor.py
sudo -u fastapi /opt/fastapi/venv/bin/pip install requests
Set up monitoring cron job
Schedule regular health checks and monitoring to ensure your FastAPI application stays healthy.
sudo crontab -u fastapi -e
Add this line to run health checks every 5 minutes:
/5 * /opt/fastapi/venv/bin/python /opt/fastapi/monitor.py >> /var/log/fastapi/monitor.log 2>&1
Configure advanced performance tuning
Optimize kernel parameters for high connections
Configure system-level settings for handling high concurrent connections and network traffic.
# Network performance tuning
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 5000
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 60
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.ip_local_port_range = 1024 65535
Memory settings
vm.overcommit_memory = 1
vm.swappiness = 1
sudo sysctl --system
For more detailed kernel optimization, see our guide on Linux kernel parameters for container workloads.
Configure systemd resource limits
Set appropriate resource limits to prevent resource exhaustion while allowing optimal performance.
[Service]
File descriptor limits
LimitNOFILE=65535
Process limits
LimitNPROC=4096
Memory limits (adjust based on your server capacity)
MemoryMax=2G
MemoryHigh=1.5G
CPU limits (optional - remove for unlimited CPU)
CPUQuota=200%
I/O limits
IOWeight=200
sudo mkdir -p /etc/systemd/system/fastapi.service.d
sudo systemctl daemon-reload
sudo systemctl restart fastapi
Verify your setup
Test your Uvicorn FastAPI deployment to ensure all components are working correctly.
# Check service status
sudo systemctl status fastapi
sudo systemctl status nginx
Test direct Uvicorn connection
curl -f http://127.0.0.1:8000/health
Test through Nginx reverse proxy
curl -f http://localhost/health
Check application metrics
curl -f http://localhost/metrics
Test API endpoint
curl -f http://localhost/api/users/123
Check logs
sudo journalctl -u fastapi -f --lines 20
sudo tail -f /var/log/nginx/fastapi_access.log
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Service won't start | Python path or virtual environment issues | sudo journalctl -u fastapi to check logs, verify paths in service file |
| 502 Bad Gateway | Uvicorn not listening or firewall blocking | sudo netstat -tlnp | grep 8000 and check Nginx upstream configuration |
| Permission denied errors | Incorrect file ownership or permissions | sudo chown -R fastapi:fastapi /opt/fastapi and use chmod 755/644 |
| High memory usage | Too many workers or memory leaks | Reduce workers in systemd service, monitor with /metrics endpoint |
| SSL certificate errors | Certbot configuration or renewal issues | sudo certbot renew --dry-run and check Nginx SSL configuration |
| Rate limiting blocking requests | Nginx rate limits too restrictive | Adjust limit_req settings in Nginx configuration |
Next steps
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Usage function
usage() {
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " -d, --domain DOMAIN Domain name for Nginx configuration (default: localhost)"
echo " -p, --port PORT Application port (default: 8000)"
echo " -w, --workers WORKERS Number of Uvicorn workers (default: 4)"
echo " -h, --help Show this help message"
exit 1
}
# Default values
DOMAIN="localhost"
APP_PORT="8000"
WORKERS="4"
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-d|--domain) DOMAIN="$2"; shift 2 ;;
-p|--port) APP_PORT="$2"; shift 2 ;;
-w|--workers) WORKERS="$2"; shift 2 ;;
-h|--help) usage ;;
*) echo "Unknown option: $1"; usage ;;
esac
done
# Error handling
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Cleaning up...${NC}"
systemctl stop fastapi-uvicorn 2>/dev/null || true
systemctl disable fastapi-uvicorn 2>/dev/null || true
rm -f /etc/systemd/system/fastapi-uvicorn.service
userdel -r fastapi 2>/dev/null || true
echo -e "${YELLOW}Cleanup completed${NC}"
}
trap cleanup ERR
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
echo -e "${GREEN}FastAPI Uvicorn Production Installer${NC}"
echo "Domain: $DOMAIN"
echo "Port: $APP_PORT"
echo "Workers: $WORKERS"
echo
# Detect OS and set package manager
echo -e "${YELLOW}[1/12] Detecting operating system...${NC}"
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update && apt upgrade -y"
PKG_INSTALL="apt install -y"
NGINX_CONF_DIR="/etc/nginx/sites-available"
NGINX_ENABLE_DIR="/etc/nginx/sites-enabled"
PYTHON_PKG="python3.11 python3.11-venv python3.11-dev python3-pip build-essential"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
NGINX_CONF_DIR="/etc/nginx/conf.d"
NGINX_ENABLE_DIR=""
PYTHON_PKG="python3.11 python3.11-devel python3-pip gcc gcc-c++ make"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
NGINX_CONF_DIR="/etc/nginx/conf.d"
NGINX_ENABLE_DIR=""
PYTHON_PKG="python3.11 python3.11-devel python3-pip gcc gcc-c++ make"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
else
echo -e "${RED}Cannot detect operating system${NC}"
exit 1
fi
echo -e "${GREEN}Detected: $PRETTY_NAME${NC}"
# Update system packages
echo -e "${YELLOW}[2/12] Updating system packages...${NC}"
$PKG_UPDATE
# Install dependencies
echo -e "${YELLOW}[3/12] Installing dependencies...${NC}"
$PKG_INSTALL $PYTHON_PKG nginx firewalld
# Create application user
echo -e "${YELLOW}[4/12] Creating application user...${NC}"
if ! id "fastapi" &>/dev/null; then
useradd --system --shell /bin/false --home-dir /opt/fastapi --create-home fastapi
fi
# Create application directory
echo -e "${YELLOW}[5/12] Setting up application directory...${NC}"
mkdir -p /opt/fastapi
chown fastapi:fastapi /opt/fastapi
chmod 755 /opt/fastapi
# Create Python virtual environment
echo -e "${YELLOW}[6/12] Creating Python virtual environment...${NC}"
sudo -u fastapi python3.11 -m venv /opt/fastapi/venv
sudo -u fastapi /opt/fastapi/venv/bin/pip install --upgrade pip
sudo -u fastapi /opt/fastapi/venv/bin/pip install fastapi uvicorn[standard] python-multipart psutil
# Create FastAPI application
echo -e "${YELLOW}[7/12] Creating FastAPI application...${NC}"
cat > /opt/fastapi/app.py << 'EOF'
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
import uvicorn
import os
import psutil
from datetime import datetime
app = FastAPI(title="FastAPI with Uvicorn", version="1.0.0")
@app.get("/")
async def root():
return {"message": "FastAPI with Uvicorn is running", "timestamp": datetime.now().isoformat()}
@app.get("/health")
async def health_check():
return {
"status": "healthy",
"timestamp": datetime.now().isoformat(),
"version": "1.0.0"
}
@app.get("/metrics")
async def metrics():
try:
process = psutil.Process()
return {
"memory_usage": round(process.memory_info().rss / 1024 / 1024, 2),
"cpu_percent": process.cpu_percent(),
"connections": len(process.connections()),
"uptime": datetime.now().isoformat()
}
except Exception as e:
return {"error": str(e)}
@app.get("/api/users/{user_id}")
async def get_user(user_id: int):
if user_id < 1:
raise HTTPException(status_code=400, detail="Invalid user ID")
return {"user_id": user_id, "username": f"user{user_id}", "active": True}
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=8000)
EOF
chown fastapi:fastapi /opt/fastapi/app.py
chmod 644 /opt/fastapi/app.py
# Create systemd service
echo -e "${YELLOW}[8/12] Creating systemd service...${NC}"
cat > /etc/systemd/system/fastapi-uvicorn.service << EOF
[Unit]
Description=FastAPI application with Uvicorn
After=network.target
Wants=network.target
[Service]
Type=exec
User=fastapi
Group=fastapi
WorkingDirectory=/opt/fastapi
Environment=PATH=/opt/fastapi/venv/bin
Environment=PYTHONPATH=/opt/fastapi
Environment=PYTHONUNBUFFERED=1
ExecStart=/opt/fastapi/venv/bin/uvicorn app:app --host 127.0.0.1 --port $APP_PORT --workers $WORKERS --access-log --log-level info
ExecReload=/bin/kill -HUP \$MAINPID
KillMode=mixed
Restart=always
RestartSec=10
SyslogIdentifier=fastapi-uvicorn
# Security settings
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/opt/fastapi
CapabilityBoundingSet=
# Resource limits
LimitNOFILE=65535
LimitNPROC=4096
[Install]
WantedBy=multi-user.target
EOF
# Configure Nginx
echo -e "${YELLOW}[9/12] Configuring Nginx...${NC}"
if [[ -n "$NGINX_ENABLE_DIR" ]]; then
# Debian-based systems
cat > "$NGINX_CONF_DIR/fastapi" << EOF
server {
listen 80;
server_name $DOMAIN;
client_max_body_size 16M;
location / {
proxy_pass http://127.0.0.1:$APP_PORT;
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 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
location /health {
proxy_pass http://127.0.0.1:$APP_PORT/health;
access_log off;
}
}
EOF
ln -sf "$NGINX_CONF_DIR/fastapi" "$NGINX_ENABLE_DIR/"
else
# RHEL-based systems
cat > "$NGINX_CONF_DIR/fastapi.conf" << EOF
server {
listen 80;
server_name $DOMAIN;
client_max_body_size 16M;
location / {
proxy_pass http://127.0.0.1:$APP_PORT;
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 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
location /health {
proxy_pass http://127.0.0.1:$APP_PORT/health;
access_log off;
}
}
EOF
fi
# Configure firewall
echo -e "${YELLOW}[10/12] Configuring firewall...${NC}"
systemctl enable --now firewalld
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
# Start and enable services
echo -e "${YELLOW}[11/12] Starting services...${NC}"
systemctl daemon-reload
systemctl enable --now fastapi-uvicorn
systemctl enable --now nginx
# Wait for service to be ready
sleep 5
# Verify installation
echo -e "${YELLOW}[12/12] Verifying installation...${NC}"
if systemctl is-active --quiet fastapi-uvicorn; then
echo -e "${GREEN}✓ FastAPI Uvicorn service is running${NC}"
else
echo -e "${RED}✗ FastAPI Uvicorn service failed to start${NC}"
systemctl status fastapi-uvicorn --no-pager
exit 1
fi
if systemctl is-active --quiet nginx; then
echo -e "${GREEN}✓ Nginx service is running${NC}"
else
echo -e "${RED}✗ Nginx service failed to start${NC}"
systemctl status nginx --no-pager
exit 1
fi
# Test health endpoint
if curl -s http://localhost:$APP_PORT/health >/dev/null; then
echo -e "${GREEN}✓ Health endpoint is responding${NC}"
else
echo -e "${RED}✗ Health endpoint is not responding${NC}"
fi
echo
echo -e "${GREEN}Installation completed successfully!${NC}"
echo
echo "Application URL: http://$DOMAIN"
echo "Health check: http://$DOMAIN/health"
echo "Metrics: http://$DOMAIN/metrics"
echo
echo "Management commands:"
echo " systemctl status fastapi-uvicorn"
echo " systemctl restart fastapi-uvicorn"
echo " journalctl -u fastapi-uvicorn -f"
echo
echo "Application files: /opt/fastapi/"
echo "Logs: journalctl -u fastapi-uvicorn"
Review the script before running. Execute with: bash install.sh