
Setting Up uWSGI and Nginx to Serve Python Apps on Ubuntu 24
Setting up a production-grade Python web application involves choosing the right combination of tools to handle traffic efficiently and maintain server stability. uWSGI and Nginx represent one of the most robust solutions for deploying Python apps, where uWSGI serves as the application server that interfaces with your Python code, while Nginx acts as a reverse proxy handling static files and managing incoming connections. This guide will walk you through the complete setup process on Ubuntu 24, covering everything from basic installation to performance optimization and troubleshooting common deployment issues.
How uWSGI and Nginx Work Together
The uWSGI-Nginx combination creates a two-tier architecture that separates concerns effectively. Nginx receives HTTP requests from clients and handles static content directly, while forwarding dynamic requests to uWSGI through a Unix socket or TCP connection. uWSGI then executes your Python application code and returns responses back through the chain.
This setup offers several advantages over running Python apps directly:
- Better performance through load balancing and connection pooling
- Enhanced security by isolating the application server from direct internet access
- Efficient static file serving without involving Python
- Process management and automatic restarts on failures
- SSL termination and advanced routing capabilities
Component | Role | Handles |
---|---|---|
Nginx | Reverse Proxy | HTTP requests, static files, SSL, load balancing |
uWSGI | Application Server | Python code execution, process management |
Unix Socket | Communication | Fast inter-process communication |
Step-by-Step Setup Guide
Let’s start with a fresh Ubuntu 24.04 system and build everything from scratch. This approach ensures you understand each component and can troubleshoot issues effectively.
Installing Required Packages
First, update your system and install the necessary packages:
sudo apt update && sudo apt upgrade -y
sudo apt install python3 python3-pip python3-venv nginx uwsgi uwsgi-plugin-python3
sudo systemctl enable nginx
sudo systemctl start nginx
Creating a Sample Python Application
We’ll create a simple Flask application to demonstrate the setup. Create a project directory and set up a virtual environment:
sudo mkdir -p /var/www/myapp
sudo chown $USER:$USER /var/www/myapp
cd /var/www/myapp
python3 -m venv venv
source venv/bin/activate
pip install flask
Create a basic Flask application in app.py
:
from flask import Flask, jsonify
import os
import time
app = Flask(__name__)
@app.route('/')
def hello():
return jsonify({
'message': 'Hello from uWSGI + Nginx!',
'pid': os.getpid(),
'timestamp': time.time()
})
@app.route('/health')
def health():
return jsonify({'status': 'healthy'})
if __name__ == '__main__':
app.run(debug=True)
Create a WSGI entry point in wsgi.py
:
from app import app
if __name__ == "__main__":
app.run()
Configuring uWSGI
Create a uWSGI configuration file at /var/www/myapp/uwsgi.ini
:
[uwsgi]
module = wsgi:app
master = true
processes = 4
threads = 2
socket = /var/www/myapp/myapp.sock
chmod-socket = 666
vacuum = true
die-on-term = true
logto = /var/log/uwsgi/myapp.log
# Performance optimizations
buffer-size = 32768
post-buffering = 8192
harakiri = 60
max-requests = 5000
# Security
uid = www-data
gid = www-data
# Python specific
home = /var/www/myapp/venv
chdir = /var/www/myapp
Create the log directory and set permissions:
sudo mkdir -p /var/log/uwsgi
sudo chown www-data:www-data /var/log/uwsgi
sudo chown -R www-data:www-data /var/www/myapp
Creating a Systemd Service
Create a systemd service file at /etc/systemd/system/myapp.service
:
[Unit]
Description=uWSGI instance to serve myapp
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/myapp
Environment="PATH=/var/www/myapp/venv/bin"
ExecStart=/usr/bin/uwsgi --ini uwsgi.ini
Restart=always
[Install]
WantedBy=multi-user.target
Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp
sudo systemctl status myapp
Configuring Nginx
Create an Nginx server block at /etc/nginx/sites-available/myapp
:
server {
listen 80;
server_name your-domain.com www.your-domain.com;
location / {
include uwsgi_params;
uwsgi_pass unix:/var/www/myapp/myapp.sock;
uwsgi_read_timeout 300;
uwsgi_send_timeout 300;
}
location /static {
alias /var/www/myapp/static;
expires 30d;
add_header Cache-Control "public, no-transform";
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
# Logging
access_log /var/log/nginx/myapp_access.log;
error_log /var/log/nginx/myapp_error.log;
# File upload limit
client_max_body_size 100M;
}
Enable the site and restart Nginx:
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled
sudo nginx -t
sudo systemctl restart nginx
Performance Optimization and Monitoring
Once your basic setup is working, you’ll want to tune it for production performance. Here are some key optimizations:
uWSGI Performance Tuning
The optimal number of processes and threads depends on your hardware and application characteristics. A good starting point is:
# For CPU-bound applications
processes = (2 x CPU cores)
threads = 1
# For I/O-bound applications
processes = CPU cores
threads = 2-4
Add these performance settings to your uwsgi.ini
:
# Memory optimizations
max-requests = 5000
max-requests-delta = 100
reload-on-rss = 512
# Connection handling
listen = 1024
buffer-size = 32768
# Enable cheaper subsystem for dynamic scaling
cheaper-algo = busyness
processes = 8
cheaper = 2
cheaper-initial = 4
cheaper-overload = 30
cheaper-step = 2
Nginx Performance Settings
Update your Nginx configuration for better performance:
server {
# ... existing configuration ...
# Enable gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/xml+rss
application/json;
# Connection optimizations
keepalive_timeout 65;
keepalive_requests 100;
# Buffer optimizations
client_body_buffer_size 128k;
client_header_buffer_size 3m;
large_client_header_buffers 4 256k;
}
Real-World Use Cases and Examples
This uWSGI-Nginx setup works well for various Python web applications:
- Django Applications: Perfect for content management systems and e-commerce platforms
- Flask APIs: Ideal for microservices and REST API backends
- FastAPI Services: Great for high-performance async applications
- Machine Learning Models: Serving ML predictions through web interfaces
Django Configuration Example
For Django projects, your uwsgi.ini
would look slightly different:
[uwsgi]
module = myproject.wsgi:application
master = true
processes = 4
threads = 2
socket = /var/www/myproject/myproject.sock
chmod-socket = 666
# Django specific settings
env = DJANGO_SETTINGS_MODULE=myproject.settings.production
static-map = /static=/var/www/myproject/staticfiles
# ... rest of configuration ...
Comparison with Alternative Solutions
Solution | Performance | Complexity | Memory Usage | Best For |
---|---|---|---|---|
uWSGI + Nginx | Excellent | Medium | Moderate | Production apps |
Gunicorn + Nginx | Good | Low | Moderate | Simple deployments |
Apache + mod_wsgi | Good | Medium | Higher | Legacy systems |
Docker + Kubernetes | Excellent | High | Higher | Microservices |
Common Issues and Troubleshooting
Here are the most frequent problems you’ll encounter and their solutions:
Socket Permission Issues
If you see “Permission denied” errors, check socket permissions:
# Check socket exists and permissions
ls -la /var/www/myapp/myapp.sock
# Fix ownership if needed
sudo chown www-data:www-data /var/www/myapp/myapp.sock
# Verify Nginx can access the socket
sudo -u www-data test -r /var/www/myapp/myapp.sock && echo "OK" || echo "FAIL"
High Memory Usage
Monitor and limit memory consumption:
# Add to uwsgi.ini
reload-on-rss = 512 # Restart worker if RSS > 512MB
evil-reload-on-rss = 1024 # Force restart if RSS > 1GB
# Monitor memory usage
ps aux | grep uwsgi
sudo systemctl status myapp
Slow Response Times
Debug performance issues:
# Enable uWSGI stats
stats = 127.0.0.1:9191
# Check stats
uwsgi --connect-and-read 127.0.0.1:9191
# Monitor Nginx logs
sudo tail -f /var/log/nginx/myapp_access.log
Application Won’t Start
Check logs systematically:
# Check systemd service
sudo journalctl -u myapp -f
# Check uWSGI logs
sudo tail -f /var/log/uwsgi/myapp.log
# Check Nginx logs
sudo tail -f /var/log/nginx/error.log
# Test uWSGI configuration
sudo -u www-data uwsgi --ini /var/www/myapp/uwsgi.ini --check-static
Security Best Practices
Implement these security measures for production deployments:
- Run uWSGI processes under a dedicated user account
- Use Unix sockets instead of TCP for better isolation
- Set appropriate file permissions (644 for files, 755 for directories)
- Enable SSL/TLS termination at Nginx level
- Configure rate limiting and request size limits
- Regularly update system packages and Python dependencies
Example security-hardened Nginx configuration:
server {
# ... existing configuration ...
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req zone=api burst=20 nodelay;
# Hide server information
server_tokens off;
# Prevent access to sensitive files
location ~ /\. {
deny all;
}
location ~ \.(ini|conf)$ {
deny all;
}
}
This setup provides a solid foundation for deploying Python applications in production. The combination of uWSGI’s robust process management with Nginx’s efficient request handling creates a scalable and maintainable architecture. For more advanced configurations and troubleshooting, refer to the official uWSGI documentation and Nginx documentation.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.