BLOG POSTS
    MangoHost Blog / Serving Flask Applications with uWSGI and Nginx on Ubuntu 24
Serving Flask Applications with uWSGI and Nginx on Ubuntu 24

Serving Flask Applications with uWSGI and Nginx on Ubuntu 24

Setting up a production-ready Flask application involves more than just running your development server. You need a robust web server configuration that can handle concurrent requests, serve static files efficiently, and maintain stability under load. This guide walks you through configuring Flask with uWSGI as the application server and Nginx as the reverse proxy on Ubuntu 24, covering everything from initial setup to production optimization and troubleshooting common deployment issues.

How the Stack Works

The Flask + uWSGI + Nginx stack creates a three-tier architecture that separates concerns effectively. Nginx handles incoming HTTP requests, serves static files directly, and forwards dynamic requests to uWSGI. uWSGI acts as the WSGI server, running your Flask application in multiple worker processes for better performance and stability.

Here’s the request flow:

  • Client sends HTTP request to Nginx (port 80/443)
  • Nginx serves static files directly or forwards to uWSGI via Unix socket
  • uWSGI processes the request through Flask application
  • Response travels back through uWSGI to Nginx to client

This setup provides several advantages over running Flask’s development server: process isolation, automatic restart on crashes, load balancing across workers, efficient static file serving, and SSL termination at the Nginx level.

Prerequisites and Installation

Start with a fresh Ubuntu 24 system and update the package manager:

sudo apt update && sudo apt upgrade -y
sudo apt install python3 python3-pip python3-venv nginx -y

Create a dedicated user for your application to follow security best practices:

sudo adduser --system --group --home /var/www/flaskapp flaskapp
sudo mkdir -p /var/www/flaskapp
sudo chown flaskapp:flaskapp /var/www/flaskapp

Switch to the application user and set up the Python environment:

sudo -u flaskapp bash
cd /var/www/flaskapp
python3 -m venv venv
source venv/bin/activate
pip install flask uwsgi

Creating a Sample Flask Application

Create a basic Flask application to test the setup. This example includes both dynamic routes and static file serving:

# /var/www/flaskapp/app.py
from flask import Flask, jsonify, render_template_string
import os

app = Flask(__name__)

@app.route('/')
def home():
    return render_template_string('''
    <h1>Flask with uWSGI and Nginx</h1>
    <p>Server: {{ server_info }}</p>
    <p>Process ID: {{ pid }}</p>
    ''', server_info=os.uname().nodename, pid=os.getpid())

@app.route('/api/health')
def health():
    return jsonify({
        'status': 'healthy',
        'pid': os.getpid(),
        'server': os.uname().nodename
    })

@app.route('/api/test')
def test():
    return jsonify({'message': 'Flask app is running correctly'})

if __name__ == '__main__':
    app.run(debug=True)

Create a WSGI entry point for uWSGI:

# /var/www/flaskapp/wsgi.py
from app import app

if __name__ == "__main__":
    app.run()

Configuring uWSGI

uWSGI configuration can be done through INI files, which provide better maintainability than command-line arguments. Create the configuration file:

# /var/www/flaskapp/uwsgi.ini
[uwsgi]
module = wsgi:app
master = true
processes = 4
socket = /var/www/flaskapp/flaskapp.sock
chmod-socket = 666
vacuum = true
die-on-term = true
logto = /var/www/flaskapp/uwsgi.log

Key configuration parameters explained:

  • processes: Number of worker processes (typically CPU cores Γ— 2)
  • socket: Unix socket for communication with Nginx
  • chmod-socket: Permissions for the socket file
  • vacuum: Clean up socket file on exit
  • die-on-term: Proper shutdown handling

Test the uWSGI configuration manually first:

cd /var/www/flaskapp
source venv/bin/activate
uwsgi --ini uwsgi.ini

If everything works correctly, you should see uWSGI starting without errors and creating the socket file.

Setting Up Nginx Configuration

Create an Nginx server block for your Flask application:

# /etc/nginx/sites-available/flaskapp
server {
    listen 80;
    server_name your-domain.com;  # Replace with your domain
    
    location / {
        include uwsgi_params;
        uwsgi_pass unix:/var/www/flaskapp/flaskapp.sock;
    }
    
    location /static {
        alias /var/www/flaskapp/static;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
    
    # 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;
    
    # 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;
}

Enable the site and test the Nginx configuration:

sudo ln -s /etc/nginx/sites-available/flaskapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx

Creating Systemd Service

For production deployment, create a systemd service to manage the uWSGI process automatically:

# /etc/systemd/system/flaskapp.service
[Unit]
Description=uWSGI instance to serve flaskapp
After=network.target

[Service]
User=flaskapp
Group=flaskapp
WorkingDirectory=/var/www/flaskapp
Environment="PATH=/var/www/flaskapp/venv/bin"
ExecStart=/var/www/flaskapp/venv/bin/uwsgi --ini uwsgi.ini
Restart=always

[Install]
WantedBy=multi-user.target

Enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable flaskapp
sudo systemctl start flaskapp
sudo systemctl status flaskapp

Performance Optimization and Tuning

The default configuration works for basic setups, but production environments need optimization. Here’s an enhanced uWSGI configuration:

# /var/www/flaskapp/uwsgi-production.ini
[uwsgi]
module = wsgi:app
master = true
processes = 4
threads = 2
socket = /var/www/flaskapp/flaskapp.sock
chmod-socket = 666
vacuum = true
die-on-term = true

# Performance tuning
max-requests = 1000
max-requests-delta = 50
harakiri = 30
buffer-size = 32768

# Memory management  
reload-on-rss = 512
no-orphans = true

# Logging
logto = /var/www/flaskapp/uwsgi.log
log-maxsize = 50000000
log-backupname = /var/www/flaskapp/uwsgi.log.old

# Stats server (optional)
stats = 127.0.0.1:9191

Performance comparison between different configurations:

Configuration Requests/sec Memory Usage Response Time (avg)
Flask dev server 50-100 30-50MB 20-50ms
Basic uWSGI (2 processes) 300-500 80-120MB 10-20ms
Optimized uWSGI (4 processes, 2 threads) 800-1200 150-200MB 5-15ms
With Nginx caching 2000-5000 150-200MB 2-8ms

Common Issues and Troubleshooting

Here are the most frequent problems you’ll encounter and their solutions:

502 Bad Gateway Error

This usually indicates uWSGI isn’t running or socket permissions are wrong:

# Check if uWSGI is running
sudo systemctl status flaskapp

# Check socket file exists and has correct permissions
ls -la /var/www/flaskapp/flaskapp.sock

# Check Nginx error logs
sudo tail -f /var/log/nginx/error.log

# Check uWSGI logs
sudo tail -f /var/www/flaskapp/uwsgi.log

Permission Denied Errors

Ensure correct ownership and permissions:

sudo chown -R flaskapp:flaskapp /var/www/flaskapp
sudo chmod 755 /var/www/flaskapp
sudo usermod -a -G flaskapp www-data

High Memory Usage

Add memory limits to prevent runaway processes:

# Add to uwsgi.ini
reload-on-rss = 512
max-requests = 1000

Slow Response Times

Enable uWSGI stats to monitor performance:

# Add to uwsgi.ini
stats = 127.0.0.1:9191

# Install uwsgitop for monitoring
pip install uwsgitop
uwsgitop 127.0.0.1:9191

Security Best Practices

Implement these security measures for production deployments:

Update the Nginx configuration with enhanced security:

server {
    listen 80;
    server_name your-domain.com;
    
    # Rate limiting
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    limit_req zone=api burst=20 nodelay;
    
    # Hide server version
    server_tokens off;
    
    # 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;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self'" always;
    
    location / {
        include uwsgi_params;
        uwsgi_pass unix:/var/www/flaskapp/flaskapp.sock;
        
        # Additional uWSGI security
        uwsgi_param HTTP_X_FORWARDED_PROTO $scheme;
        uwsgi_param HTTP_X_REAL_IP $remote_addr;
    }
}

Configure firewall rules:

sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Real-World Use Cases and Examples

This setup is ideal for several scenarios:

API Services: The combination provides excellent performance for REST APIs, handling thousands of concurrent requests with proper load balancing.

Content Management Systems: Nginx efficiently serves uploaded media files while Flask handles dynamic content generation.

Microservices Architecture: Deploy multiple Flask applications with different Nginx upstream configurations for service routing.

Example multi-application setup:

# /etc/nginx/sites-available/multi-flask
upstream auth_service {
    server unix:/var/www/auth/auth.sock;
}

upstream api_service {  
    server unix:/var/www/api/api.sock;
}

server {
    listen 80;
    server_name api.yourdomain.com;
    
    location /auth/ {
        include uwsgi_params;
        uwsgi_pass auth_service;
    }
    
    location /api/ {
        include uwsgi_params;
        uwsgi_pass api_service;
    }
}

Monitoring and Maintenance

Set up log rotation to prevent disk space issues:

# /etc/logrotate.d/flaskapp
/var/www/flaskapp/uwsgi.log {
    daily
    missingok
    rotate 52
    compress
    delaycompress
    notifempty
    create 644 flaskapp flaskapp
    postrotate
        systemctl reload flaskapp
    endscript
}

Create a monitoring script for health checks:

#!/bin/bash
# /var/www/flaskapp/healthcheck.sh
response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/api/health)
if [ $response != "200" ]; then
    echo "Health check failed with code: $response"
    systemctl restart flaskapp
    systemctl restart nginx
fi

This setup provides a solid foundation for production Flask deployments. The key is starting with a working basic configuration and gradually adding optimizations based on your specific requirements and traffic patterns. Regular monitoring and maintenance ensure long-term stability and performance.

For more detailed information, check the official documentation: 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