Set up a production-ready Python web application stack with uWSGI as the WSGI server and Nginx as a reverse proxy. Includes SSL certificate setup, systemd service configuration, and troubleshooting guide for Django and Flask deployments.
Prerequisites
- Root or sudo access
- Domain name pointing to your server
- Basic Python knowledge
What this solves
uWSGI serves as the bridge between your Python web applications (Django, Flask, FastAPI) and a web server like Nginx. Unlike development servers, uWSGI handles multiple concurrent requests efficiently and provides production features like process management, memory monitoring, and graceful reloads. This setup gives you a scalable foundation for Python web applications with proper SSL termination and static file serving.
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, pip, and development tools
Install Python 3 and the development headers needed to compile uWSGI from source if required.
sudo apt install -y python3 python3-pip python3-dev python3-venv gcc libc6-dev
Install Nginx web server
Nginx will handle SSL termination, static file serving, and proxy requests to uWSGI.
sudo apt install -y nginx
Create application user and directory
Create a dedicated user for your Python application to improve security isolation.
sudo useradd --system --home /opt/myapp --create-home --shell /bin/bash myapp
sudo mkdir -p /opt/myapp/{app,logs,static}
sudo chown -R myapp:myapp /opt/myapp
Install uWSGI system-wide
Install uWSGI globally so it can be managed by systemd. This approach works better than virtual environment installations for production services.
sudo apt install -y uwsgi uwsgi-plugin-python3
Create sample Python application
Create a simple WSGI application to test the setup. This works with any WSGI-compliant framework.
def application(environ, start_response):
status = '200 OK'
headers = [('Content-type', 'text/html; charset=utf-8')]
start_response(status, headers)
response_body = b'''
uWSGI Test
uWSGI is working!
Your Python application server is running correctly.
Server: uWSGI + Nginx
'''
return [response_body]
Configure uWSGI application settings
Create the uWSGI configuration file with production-ready settings including process management and logging.
[uwsgi]
Application settings
module = wsgi:application
chdir = /opt/myapp/app
home = /opt/myapp/venv
Process management
master = true
processes = 4
threads = 2
max-requests = 1000
max-requests-delta = 100
Socket settings
socket = /opt/myapp/uwsgi.sock
chmod-socket = 664
vacuum = true
die-on-term = true
User and group
uid = myapp
gid = myapp
Logging
logto = /opt/myapp/logs/uwsgi.log
log-maxsize = 50000000
log-backupname = /opt/myapp/logs/uwsgi.log.old
Security
disable-logging = false
ignore-sigpipe = true
ignore-write-errors = true
disable-write-exception = true
Performance
enable-threads = true
single-interpreter = true
lazy-apps = true
preload-app = true
Set correct file permissions
Configure ownership and permissions for the application files and directories.
sudo chown -R myapp:myapp /opt/myapp
sudo chmod 755 /opt/myapp
sudo chmod 755 /opt/myapp/app
sudo chmod 644 /opt/myapp/app/wsgi.py
sudo chmod 644 /opt/myapp/uwsgi.ini
sudo chmod 755 /opt/myapp/logs
sudo chmod 755 /opt/myapp/static
Create systemd service for uWSGI
Configure uWSGI as a systemd service for automatic startup and process management.
[Unit]
Description=uWSGI instance to serve myapp
After=network.target
[Service]
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/uwsgi --ini uwsgi.ini
Restart=always
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all
StandardError=syslog
[Install]
WantedBy=multi-user.target
Configure Nginx reverse proxy
Set up Nginx to proxy requests to uWSGI and serve static files directly.
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;
# Static files
location /static/ {
alias /opt/myapp/static/;
expires 30d;
add_header Cache-Control "public, no-transform";
}
# Media files (uploads)
location /media/ {
alias /opt/myapp/media/;
expires 7d;
}
# Main application
location / {
include uwsgi_params;
uwsgi_pass unix:/opt/myapp/uwsgi.sock;
uwsgi_read_timeout 300;
uwsgi_connect_timeout 300;
uwsgi_send_timeout 300;
# Forward real IP
uwsgi_param HTTP_X_FORWARDED_FOR $remote_addr;
uwsgi_param HTTP_X_FORWARDED_PROTO $scheme;
uwsgi_param HTTP_HOST $server_name;
}
# Deny access to sensitive files
location ~ /\.(ht|env|git) {
deny all;
}
# Custom error pages
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
Enable Nginx site configuration
Activate the site configuration and test the Nginx syntax before restarting.
sudo ln -sf /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
Start and enable services
Start both uWSGI and Nginx services and configure them to start automatically on boot.
sudo systemctl daemon-reload
sudo systemctl enable --now myapp-uwsgi
sudo systemctl enable --now nginx
sudo systemctl status myapp-uwsgi
sudo systemctl status nginx
Install Certbot for SSL certificates
Set up Let's Encrypt SSL certificates for secure HTTPS connections. This uses the official Certbot from Let's Encrypt.
sudo apt install -y certbot python3-certbot-nginx
Obtain SSL certificate
Generate SSL certificates for your domain. Replace example.com with your actual domain name.
sudo certbot --nginx -d example.com -d www.example.com
Set up automatic certificate renewal
Configure automatic renewal of SSL certificates to prevent expiration.
sudo systemctl enable --now certbot.timer
sudo systemctl status certbot.timer
Configure for Django applications
For Django applications, you need to modify the uWSGI configuration to point to your Django project's WSGI module.
[uwsgi]
Django settings
module = myproject.wsgi:application
chdir = /opt/myapp/app
home = /opt/myapp/venv
env = DJANGO_SETTINGS_MODULE=myproject.settings.production
Process management
master = true
processes = 4
threads = 2
max-requests = 1000
max-requests-delta = 100
Socket settings
socket = /opt/myapp/uwsgi.sock
chmod-socket = 664
vacuum = true
die-on-term = true
User and group
uid = myapp
gid = myapp
Django static files
static-map = /static=/opt/myapp/static
static-expires-uri = /static/.* 3600
Logging
logto = /opt/myapp/logs/uwsgi.log
log-maxsize = 50000000
log-backupname = /opt/myapp/logs/uwsgi.log.old
Performance
enable-threads = true
single-interpreter = true
lazy-apps = true
preload-app = true
Configure for Flask applications
Flask applications require a simple WSGI configuration pointing to your Flask app instance.
from myflaskapp import create_app
Create Flask application instance
application = create_app()
if __name__ == "__main__":
application.run()
[uwsgi]
Flask settings
module = wsgi:application
chdir = /opt/myapp/app
home = /opt/myapp/venv
callable = application
Process management
master = true
processes = 2
threads = 4
max-requests = 1000
Socket and permissions
socket = /opt/myapp/uwsgi.sock
chmod-socket = 664
vacuum = true
die-on-term = true
uid = myapp
gid = myapp
Logging
logto = /opt/myapp/logs/uwsgi.log
req-logger = file:/opt/myapp/logs/requests.log
Performance
enable-threads = true
lazy-apps = true
Performance tuning
Optimize uWSGI performance based on your server resources and traffic patterns.
| Setting | Purpose | Recommended Value |
|---|---|---|
| processes | Number of worker processes | CPU cores × 2 |
| threads | Threads per process | 2-4 for I/O heavy apps |
| max-requests | Requests before worker restart | 1000-5000 |
| buffer-size | Request buffer size | 32768 for large headers |
| harakiri | Request timeout (seconds) | 30-300 depending on app |
[uwsgi]
Your existing configuration...
Performance tuning
buffer-size = 32768
harakiri = 120
harakiri-verbose = true
max-worker-lifetime = 3600
reload-on-rss = 512
worker-reload-mercy = 60
Memory reporting
memory-report = true
Cheaper scaling (dynamic workers)
cheaper = 2
cheaper-initial = 2
cheaper-step = 1
cheaper-algo = spare2
cheaper-overload = 30
Verify your setup
Test that your uWSGI and Nginx configuration is working correctly.
# Check service status
sudo systemctl status myapp-uwsgi
sudo systemctl status nginx
Test HTTP response
curl -I http://example.com
Test HTTPS response
curl -I https://example.com
Check uWSGI socket
ls -la /opt/myapp/uwsgi.sock
Monitor logs
sudo tail -f /opt/myapp/logs/uwsgi.log
sudo tail -f /var/log/nginx/error.log
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| 502 Bad Gateway | uWSGI socket not accessible | Check socket permissions: chmod 664 /opt/myapp/uwsgi.sock |
| uWSGI won't start | Configuration syntax error | Test config: uwsgi --ini /opt/myapp/uwsgi.ini --check-static |
| Permission denied errors | Wrong file ownership | Fix ownership: sudo chown -R myapp:myapp /opt/myapp |
| Static files not loading | Nginx can't read static directory | Check permissions: sudo chmod 755 /opt/myapp/static |
| SSL certificate errors | Certbot couldn't verify domain | Check DNS and firewall: sudo certbot certificates |
| High memory usage | Memory leaks in application | Enable worker recycling: max-requests = 1000 |
| Slow response times | Insufficient worker processes | Increase processes: processes = 4 |
Next steps
- Monitor Django applications with Prometheus and Grafana for comprehensive performance insights
- Configure NGINX reverse proxy with SSL termination and load balancing for high availability
- Configure Linux system services with systemctl and service management
- Set up automated SSL certificate management with Certbot
- Configure uWSGI application scaling and load balancing
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Default configuration
APP_NAME="${1:-myapp}"
APP_DOMAIN="${2:-localhost}"
APP_USER="$APP_NAME"
APP_HOME="/opt/$APP_NAME"
TOTAL_STEPS=12
# Cleanup function for rollback
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Cleaning up...${NC}"
systemctl stop "$APP_NAME-uwsgi" 2>/dev/null || true
systemctl disable "$APP_NAME-uwsgi" 2>/dev/null || true
rm -f "/etc/systemd/system/$APP_NAME-uwsgi.service"
rm -f "/etc/nginx/sites-enabled/$APP_NAME" 2>/dev/null || true
rm -f "/etc/nginx/conf.d/$APP_NAME.conf" 2>/dev/null || true
userdel --remove "$APP_USER" 2>/dev/null || true
rm -rf "$APP_HOME"
systemctl reload nginx 2>/dev/null || true
echo -e "${YELLOW}Cleanup completed.${NC}"
exit 1
}
trap cleanup ERR
usage() {
echo "Usage: $0 [app_name] [domain]"
echo "Example: $0 myapp example.com"
echo "Defaults: app_name=myapp, domain=localhost"
exit 1
}
log_step() {
echo -e "${BLUE}[$1/$TOTAL_STEPS] $2${NC}"
}
log_success() {
echo -e "${GREEN}✓ $1${NC}"
}
log_warning() {
echo -e "${YELLOW}⚠ $1${NC}"
}
# Check prerequisites
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
if [[ "$#" -gt 2 ]]; then
usage
fi
# Auto-detect distribution
log_step 1 "Detecting operating system"
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
NGINX_CONF_DIR="/etc/nginx/sites-available"
NGINX_ENABLED_DIR="/etc/nginx/sites-enabled"
PYTHON_DEV="python3-dev"
UWSGI_PKG="uwsgi uwsgi-plugin-python3"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
NGINX_CONF_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR="/etc/nginx/conf.d"
PYTHON_DEV="python3-devel"
UWSGI_PKG=""
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
NGINX_CONF_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR="/etc/nginx/conf.d"
PYTHON_DEV="python3-devel"
UWSGI_PKG=""
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
log_success "Detected $PRETTY_NAME"
else
echo -e "${RED}Cannot detect OS distribution${NC}"
exit 1
fi
# Update system packages
log_step 2 "Updating system packages"
$PKG_UPDATE
log_success "System packages updated"
# Install Python and development tools
log_step 3 "Installing Python and development tools"
$PKG_INSTALL python3 python3-pip $PYTHON_DEV python3-venv gcc
if [[ "$ID" == "ubuntu" ]] || [[ "$ID" == "debian" ]]; then
$PKG_INSTALL libc6-dev
else
$PKG_INSTALL glibc-devel
fi
log_success "Python and development tools installed"
# Install Nginx
log_step 4 "Installing Nginx web server"
$PKG_INSTALL nginx
systemctl enable nginx
log_success "Nginx installed and enabled"
# Create application user and directories
log_step 5 "Creating application user and directories"
useradd --system --home "$APP_HOME" --create-home --shell /bin/bash "$APP_USER" || true
mkdir -p "$APP_HOME"/{app,logs,static}
chown -R "$APP_USER:$APP_USER" "$APP_HOME"
chmod 755 "$APP_HOME"
chmod 755 "$APP_HOME"/{app,logs,static}
log_success "Application user $APP_USER created"
# Install uWSGI
log_step 6 "Installing uWSGI application server"
if [[ -n "$UWSGI_PKG" ]]; then
$PKG_INSTALL $UWSGI_PKG
UWSGI_BIN="/usr/bin/uwsgi"
else
pip3 install uwsgi
UWSGI_BIN=$(which uwsgi)
fi
log_success "uWSGI installed"
# Create sample WSGI application
log_step 7 "Creating sample Python application"
cat > "$APP_HOME/app/wsgi.py" << 'EOF'
def application(environ, start_response):
status = '200 OK'
headers = [('Content-type', 'text/html; charset=utf-8')]
start_response(status, headers)
response_body = b'''<!DOCTYPE html>
<html>
<head><title>uWSGI Test</title></head>
<body>
<h1>uWSGI is working!</h1>
<p>Your Python application server is running correctly.</p>
<p><strong>Server:</strong> uWSGI + Nginx</p>
</body>
</html>'''
return [response_body]
EOF
chown "$APP_USER:$APP_USER" "$APP_HOME/app/wsgi.py"
chmod 644 "$APP_HOME/app/wsgi.py"
log_success "Sample WSGI application created"
# Configure uWSGI
log_step 8 "Configuring uWSGI application settings"
cat > "$APP_HOME/uwsgi.ini" << EOF
[uwsgi]
; Application settings
module = wsgi:application
chdir = $APP_HOME/app
; Process management
master = true
processes = 4
threads = 2
max-requests = 1000
max-requests-delta = 100
; Socket settings
socket = $APP_HOME/uwsgi.sock
chmod-socket = 664
vacuum = true
die-on-term = true
; User and group
uid = $APP_USER
gid = $APP_USER
; Logging
logto = $APP_HOME/logs/uwsgi.log
log-maxsize = 50000000
log-backupname = $APP_HOME/logs/uwsgi.log.old
; Security
disable-logging = false
ignore-sigpipe = true
ignore-write-errors = true
disable-write-exception = true
; Performance
enable-threads = true
single-interpreter = true
lazy-apps = true
preload-app = true
EOF
chown "$APP_USER:$APP_USER" "$APP_HOME/uwsgi.ini"
chmod 644 "$APP_HOME/uwsgi.ini"
log_success "uWSGI configuration created"
# Create systemd service
log_step 9 "Creating systemd service for uWSGI"
cat > "/etc/systemd/system/$APP_NAME-uwsgi.service" << EOF
[Unit]
Description=uWSGI instance to serve $APP_NAME
After=network.target
[Service]
User=$APP_USER
Group=$APP_USER
WorkingDirectory=$APP_HOME
ExecStart=$UWSGI_BIN --ini uwsgi.ini
Restart=always
RestartSec=10
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable "$APP_NAME-uwsgi"
log_success "Systemd service created and enabled"
# Configure Nginx
log_step 10 "Configuring Nginx reverse proxy"
if [[ "$NGINX_CONF_DIR" == "/etc/nginx/sites-available" ]]; then
NGINX_CONF_FILE="$NGINX_CONF_DIR/$APP_NAME"
NGINX_ENABLED_FILE="$NGINX_ENABLED_DIR/$APP_NAME"
else
NGINX_CONF_FILE="$NGINX_CONF_DIR/$APP_NAME.conf"
NGINX_ENABLED_FILE="$NGINX_CONF_FILE"
fi
cat > "$NGINX_CONF_FILE" << EOF
server {
listen 80;
server_name $APP_DOMAIN;
location / {
include uwsgi_params;
uwsgi_pass unix:$APP_HOME/uwsgi.sock;
uwsgi_param Host \$host;
uwsgi_param X-Real-IP \$remote_addr;
uwsgi_param X-Forwarded-For \$proxy_add_x_forwarded_for;
uwsgi_param X-Forwarded-Proto \$scheme;
}
location /static/ {
alias $APP_HOME/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
access_log $APP_HOME/logs/nginx_access.log;
error_log $APP_HOME/logs/nginx_error.log;
}
EOF
if [[ "$NGINX_CONF_DIR" == "/etc/nginx/sites-available" ]]; then
ln -sf "$NGINX_CONF_FILE" "$NGINX_ENABLED_FILE"
fi
nginx -t
log_success "Nginx configuration created and validated"
# Start services
log_step 11 "Starting services"
systemctl start "$APP_NAME-uwsgi"
systemctl restart nginx
# Configure firewall if present
if command -v ufw &> /dev/null && ufw status | grep -q "Status: active"; then
ufw allow 'Nginx Full'
log_success "UFW firewall configured"
elif command -v firewall-cmd &> /dev/null && systemctl is-active firewalld &> /dev/null; then
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
log_success "Firewalld configured"
fi
log_success "Services started successfully"
# Verification
log_step 12 "Verifying installation"
sleep 3
if systemctl is-active --quiet "$APP_NAME-uwsgi"; then
log_success "uWSGI service is running"
else
echo -e "${RED}uWSGI service failed to start${NC}"
exit 1
fi
if systemctl is-active --quiet nginx; then
log_success "Nginx service is running"
else
echo -e "${RED}Nginx service failed to start${NC}"
exit 1
fi
if [ -S "$APP_HOME/uwsgi.sock" ]; then
log_success "uWSGI socket created successfully"
else
log_warning "uWSGI socket not found, service may still be starting"
fi
echo
echo -e "${GREEN}✓ Installation completed successfully!${NC}"
echo
echo -e "${BLUE}Configuration Summary:${NC}"
echo " Application: $APP_NAME"
echo " Domain: $APP_DOMAIN"
echo " App Directory: $APP_HOME"
echo " User: $APP_USER"
echo
echo -e "${BLUE}Next Steps:${NC}"
echo " 1. Test your application: curl http://$APP_DOMAIN"
echo " 2. Replace $APP_HOME/app/wsgi.py with your actual application"
echo " 3. Configure SSL with Let's Encrypt if needed"
echo " 4. Monitor logs: journalctl -u $APP_NAME-uwsgi -f"
echo
echo -e "${BLUE}Service Management:${NC}"
echo " Start: systemctl start $APP_NAME-uwsgi"
echo " Stop: systemctl stop $APP_NAME-uwsgi"
echo " Restart: systemctl restart $APP_NAME-uwsgi"
echo " Status: systemctl status $APP_NAME-uwsgi"
trap - ERR
Review the script before running. Execute with: bash install.sh