BLOG POSTS
    MangoHost Blog / Setting Up uWSGI and Nginx to Serve Python Apps on Ubuntu 24
Setting Up uWSGI and Nginx to Serve Python Apps on Ubuntu 24

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.

Leave a reply

Your email address will not be published. Required fields are marked