Server Setup

How to Create a Python Web Service with uWSGI and NGINX on Rocky Linux

by ,


We have by far the largest RPM repository with NGINX module packages and VMODs for Varnish. If you want to install NGINX, Varnish, and lots of useful performance/security software with smooth yum upgrades for production use, this is the repository for you.
Active subscription is required.

Want to deploy a Python uWSGI NGINX stack on Rocky Linux? This comprehensive guide shows you exactly how to create a production-ready Python web service using uWSGI as the application server and NGINX as the reverse proxy. Whether you’re building an API endpoint, a microservice, or a specialized backend tool, this battle-tested stack delivers excellent performance and reliability.

The standard approach to running a website typically involves modern PHP frameworks like WordPress, Magento, or Laravel. However, when you need to build something custom, Python becomes an incredibly attractive choice due to its ease of coding and vast ecosystem of libraries.

Python is an extraordinarily powerful programming language with libraries available for virtually every use case imaginable. In fact, complex tasks that would require hundreds of lines in other languages can often be accomplished with just a few lines of Python code. From data processing and machine learning to web scraping and API development, Python’s extensive standard library combined with third-party packages from PyPI makes it the go-to choice for developers worldwide.

In this comprehensive guide, you’ll learn how to create a Python-based web service, serve it efficiently through NGINX using uWSGI, and configure everything for production-grade performance on Rocky Linux 9 or AlmaLinux 10.

NGINX and Python UWSGI app
Python uWSGI NGINX architecture diagram showing request flow from client through NGINX reverse proxy to uWSGI application serve

Why Choose Python uWSGI NGINX for Web Services?

Before diving into the technical implementation, let’s understand why this stack is an excellent choice for building web services:

The Extensive Python Library Ecosystem

Python’s package index (PyPI) hosts over 400,000 packages. Whether you need to parse XML, connect to databases, perform natural language processing, or interact with cloud services, there’s likely a well-maintained library available. Consequently, this dramatically reduces development time. Moreover, it allows you to focus on your application’s unique logic rather than reinventing the wheel.

Rapid Development with Python

Python’s clean syntax and dynamic typing enable rapid prototyping and iteration. As a result, what might take days in compiled languages can often be accomplished in hours with Python. Therefore, this makes it ideal for building and deploying web services quickly. Additionally, the large community provides excellent documentation and support.

Production-Ready Python uWSGI NGINX Performance

When combined with uWSGI and NGINX, Python web services can handle thousands of concurrent requests efficiently. Furthermore, uWSGI’s pre-forking model and NGINX’s event-driven architecture create a high-performance stack. In addition, this combination rivals solutions built in other languages while maintaining Python’s development simplicity.

Understanding the Python uWSGI NGINX Architecture

The combination of NGINX and uWSGI creates an optimal environment for serving Python applications. Specifically, each component serves a distinct purpose:

  • NGINX serves as the front-end reverse proxy, handling SSL termination, static files, and load balancing
  • uWSGI acts as the application server, managing Python worker processes and communicating with NGINX via Unix sockets
  • Python executes your application code within the uWSGI environment

This architecture offers several advantages. First, NGINX efficiently manages thousands of simultaneous connections. Second, uWSGI focuses on running Python code. Third, the separation of concerns enhances security. Finally, static content is served directly without involving Python:

  • Efficient Connection Handling: NGINX manages thousands of simultaneous connections while uWSGI focuses on running Python code
  • Enhanced Security: NGINX acts as a buffer between the internet and your Python application
  • Optimized Static Content: NGINX serves static files directly without involving Python
  • Robust Process Management: uWSGI handles worker process lifecycle, automatic restarts, and resource limits

Prerequisites for Python uWSGI NGINX Setup

For this guide, we’ll use either Rocky Linux 9 or AlmaLinux 10. Both distributions provide excellent stability and long-term support. As a result, they’re ideal for production web servers.

Installing Required Packages for Python uWSGI NGINX

First, install Python 3, pip, and the necessary development tools:

dnf -y install python3 python3-pip

Next, enable EPEL (Extra Packages for Enterprise Linux) repository. Then, install uWSGI with the Python plugin along with NGINX:

dnf -y install epel-release
dnf -y install uwsgi uwsgi-plugin-python3 nginx

After installation, verify the versions to ensure everything is properly installed:

python3 --version   # Python 3.9.x on Rocky 9, Python 3.12.x on AlmaLinux 10
uwsgi --version     # uWSGI 2.0.x
nginx -v            # nginx/1.20.x or newer

Creating the Python Application for uWSGI

Our example application will provide an API-like service that returns the organization name associated with any IP address, using WHOIS data. Consequently, this demonstrates a practical use case where Python’s rich library ecosystem shines.

Setting Up a Python Virtual Environment

Creating a virtual environment is a Python best practice that isolates your application’s dependencies from system packages. Therefore, we’ll create it in a directory that’s not publicly accessible via the web:

# Create the application directory
mkdir -p /srv/www/example.com
cd /srv/www/example.com

# Create and activate the virtual environment
python3 -m venv get-org
source ./get-org/bin/activate

# Upgrade pip and install the required library
pip install --upgrade pip
pip install ipwhois

The ipwhois library provides comprehensive WHOIS lookup functionality, including ASN (Autonomous System Number) information. As a result, we can use it to identify the organization that owns an IP address.

Creating a Test Script for Your Python Application

Let’s first verify our setup with a simple test script. Create /srv/www/example.com/get-org/test.py:

#!/usr/bin/env python3
from ipwhois import IPWhois

obj = IPWhois('1.1.1.1')
res = obj.lookup_whois()
print(res['asn_description'])

Run the test with the virtual environment activated:

cd /srv/www/example.com
source ./get-org/bin/activate
python3 get-org/test.py

Expected output:

CLOUDFLARENET, US

This confirms that our Python environment is correctly configured. Moreover, it shows the ipwhois library is working properly.

Building the uWSGI-Compatible Python Application

uWSGI requires applications to follow the WSGI (Web Server Gateway Interface) specification. Therefore, your application must provide a callable named application that accepts two arguments: the environment dictionary and a start_response function.

Create /srv/www/example.com/get-org/app.py:

#!/usr/bin/env python3
def application(env, start_response):
    """
    WSGI application that returns the organization name
    for the requesting client's IP address.
    """
    from ipwhois import IPWhois

    # Get the client's real IP address
    remote_addr = env.get('HTTP_X_REAL_IP', env.get('REMOTE_ADDR', ''))

    if not remote_addr:
        start_response('400 Bad Request', [('Content-Type', 'text/plain')])
        return [b'Could not determine client IP address']

    try:
        obj = IPWhois(remote_addr)
        res = obj.lookup_whois()
        org_name = res.get('asn_description', 'Unknown')

        start_response('200 OK', [
            ('Content-Type', 'text/plain; charset=utf-8'),
            ('Cache-Control', 'public, max-age=86400')
        ])
        return [org_name.encode('utf-8')]

    except Exception as e:
        start_response('500 Internal Server Error', [('Content-Type', 'text/plain')])
        return [b'Error looking up IP information']

This application reads the client’s IP address from the X-Real-IP header (set by NGINX) or falls back to REMOTE_ADDR. Additionally, it performs a WHOIS lookup using the ipwhois library. Furthermore, it returns the organization description with appropriate HTTP headers. The code also includes error handling for robustness and sets a 24-hour cache header since IP ownership rarely changes.

Testing uWSGI with Your Python Application

Before configuring NGINX, verify that uWSGI can serve your application:

cd /srv/www/example.com
source ./get-org/bin/activate
uwsgi --plugin python3 -H $(pwd)/get-org --http :9091 --wsgi-file get-org/app.py --master --processes 2

In another terminal, test the service:

curl -s localhost:9091

You should see the organization name for your local IP (likely something like “PRIVATE-ADDRESS-BLOCK” for localhost).

Configuring systemd Service for Python uWSGI

For production use, we need uWSGI to run as a system service that starts automatically and restarts on failure. Consequently, systemd provides the reliability we need.

Creating the systemd Service Unit File

Create /etc/systemd/system/uwsgi-example.com-get-org.service:

[Unit]
Description=Get-Org uWSGI Application
After=network.target

[Service]
User=nginx
Group=nginx
WorkingDirectory=/srv/www/example.com/get-org
ExecStart=/usr/sbin/uwsgi \\
    --ini /etc/uwsgi.d/example.com-get-org.ini \\
    --socket /run/uwsgi/example.com-get-org.sock
Restart=on-failure
RestartSec=5
KillSignal=SIGQUIT
Type=notify
StandardError=journal
NotifyAccess=all
RuntimeDirectory=uwsgi
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target

Creating the uWSGI Configuration File

Create the uWSGI configuration directory if it doesn’t exist:

mkdir -p /etc/uwsgi.d
chmod 0755 /etc/uwsgi.d

Subsequently, create /etc/uwsgi.d/example.com-get-org.ini:

[uwsgi]
# Master process management
master = true
enable-threads = true

# Performance tuning
processes = 4
threads = 2
harakiri = 60
max-requests = 5000

# Idle management - exit when not used
cheap = true
idle = 600
die-on-idle = true

# Application settings
base = /srv/www/example.com/get-org
home = %(base)
pythonpath = %(base)
wsgi-file = %(base)/app.py
manage-script-name = true
plugins = python3

# Socket configuration
chmod-socket = 660
vacuum = true
die-on-term = true

# Response headers
add-header = Vary: X-Real-IP
add-header = X-Served-By: uWSGI

# Logging
logto = /var/log/uwsgi/example.com-get-org.log
log-date = true

Then, create the log directory:

mkdir -p /var/log/uwsgi
chown nginx:nginx /var/log/uwsgi

Set proper permissions on the application directory:

chown -R nginx:nginx /srv/www/example.com/get-org

Enabling and Starting the uWSGI Service

Reload systemd and enable the service:

systemctl daemon-reload
systemctl enable --now uwsgi-example.com-get-org.service
systemctl status uwsgi-example.com-get-org.service

Configuring NGINX as Reverse Proxy for Python uWSGI

Now we’ll configure NGINX to proxy requests to our uWSGI application. For more information about NGINX configuration best practices, see our NGINX configuration guide.

Configuring NGINX Upstream for uWSGI

Add the following to your NGINX configuration, typically in /etc/nginx/conf.d/upstream.conf or within the http {} block of /etc/nginx/nginx.conf:

upstream example_com_get_org {
    server unix:/run/uwsgi/example.com-get-org.sock;
    keepalive 8;
}

NGINX Location Block for Python API

Add a location block within your server configuration (e.g., /etc/nginx/conf.d/example.com.conf):

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    # SSL configuration (adjust paths as needed)
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Your existing configuration...

    # Python API endpoint
    location = /api/get-org {
        include uwsgi_params;
        uwsgi_pass example_com_get_org;

        # Pass real client IP to the application
        uwsgi_param HTTP_X_REAL_IP $remote_addr;
        uwsgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;
        uwsgi_param HTTP_X_FORWARDED_PROTO $scheme;

        # Timeouts
        uwsgi_connect_timeout 10s;
        uwsgi_read_timeout 30s;
        uwsgi_send_timeout 30s;

        # Buffer settings
        uwsgi_buffer_size 4k;
        uwsgi_buffers 8 4k;
    }
}

Testing NGINX Configuration for Python uWSGI

Verify the configuration syntax using our NGINX Config Validator:

nginx -t

If successful, reload NGINX:

systemctl reload nginx

Testing the Complete Python uWSGI NGINX Setup

Test your newly deployed Python web service:

curl -s https://example.com/api/get-org

You should see the organization name for your client IP address.

Performance Optimization for Python uWSGI NGINX

uWSGI Performance Tuning

For high-traffic applications, consider these uWSGI optimizations:

# In your .ini file
processes = 8
threads = 4
listen = 256
max-requests = 10000
max-worker-lifetime = 86400
reload-on-rss = 512
worker-reload-mercy = 60

Here’s what each setting does:

  • processes: Number of worker processes (typically 2x CPU cores)
  • threads: Threads per process (useful for I/O-bound applications)
  • max-requests: Recycle workers after serving this many requests (prevents memory leaks)
  • reload-on-rss: Reload worker if memory exceeds this value in MB

NGINX Caching for Python Applications

For endpoints that return cacheable data, enable NGINX microcaching. First, define the cache zone in your http block:

# Define cache zone (in http block)
uwsgi_cache_path /var/cache/nginx/api levels=1:2 keys_zone=api_cache:10m max_size=100m inactive=1h;

# In location block
location = /api/get-org {
    # ... existing configuration ...

    uwsgi_cache api_cache;
    uwsgi_cache_valid 200 1h;
    uwsgi_cache_key $remote_addr;
    add_header X-Cache-Status $upstream_cache_status;
}

Security Best Practices for Python uWSGI NGINX

Rate Limiting with NGINX

Protect your API from abuse with NGINX rate limiting. First, define the limit zone. Then, apply it to your location:

# Define rate limit zone (in http block)
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

# In location block
location = /api/get-org {
    limit_req zone=api_limit burst=20 nodelay;
    # ... rest of configuration ...
}

File Permissions for Python Applications

Ensure proper file permissions:

# Application files should be owned by the service user
chown -R nginx:nginx /srv/www/example.com/get-org
chmod 750 /srv/www/example.com/get-org
chmod 640 /srv/www/example.com/get-org/app.py

# Socket directory
chmod 755 /run/uwsgi

Monitoring Your Python uWSGI NGINX Stack

Enabling uWSGI Stats Server

Enable the uWSGI stats server for monitoring:

# In your .ini file
stats = /run/uwsgi/example.com-get-org-stats.sock
stats-http = true

Access stats via:

curl --unix-socket /run/uwsgi/example.com-get-org-stats.sock http://localhost/

Log Rotation Configuration

Create /etc/logrotate.d/uwsgi:

/var/log/uwsgi/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 0640 nginx nginx
    sharedscripts
    postrotate
        /bin/systemctl reload uwsgi-example.com-get-org.service > /dev/null 2>&1 || true
    endscript
}

Troubleshooting Python uWSGI NGINX Issues

Socket Permission Denied Error

If NGINX reports “permission denied” when connecting to the uWSGI socket, check the ownership:

# Ensure both services use the same user/group
ls -la /run/uwsgi/
# The socket should show nginx:nginx ownership
# Verify socket permissions are 660

502 Bad Gateway Error

This error typically indicates uWSGI isn’t running or the socket path is incorrect. Therefore, check these items:

# Check uWSGI status
systemctl status uwsgi-example.com-get-org

# View recent logs
journalctl -u uwsgi-example.com-get-org -n 50

# Verify socket exists
ls -la /run/uwsgi/

Application Errors in Python

Check the uWSGI application log:

tail -f /var/log/uwsgi/example.com-get-org.log

Conclusion: Python uWSGI NGINX Production Setup

You’ve now learned how to create a production-ready Python web service using the powerful combination of NGINX and uWSGI. In summary, this architecture provides:

  • High Performance: uWSGI’s pre-forking model combined with NGINX’s efficient connection handling
  • Reliability: systemd integration ensures automatic restarts and proper process management
  • Security: NGINX acts as a protective layer between the internet and your Python application
  • Flexibility: Easy to extend with additional Python libraries and functionality

Python’s extensive library ecosystem means you can build virtually any type of web service. For instance, you could create simple API endpoints, complex machine learning inference servers, or data processing pipelines. All these options maintain excellent performance characteristics.

The techniques covered in this guide scale from small personal projects to enterprise-level deployments handling millions of requests daily. Combined with proper caching, rate limiting, and monitoring, your Python uWSGI NGINX web services will be ready for production traffic. Finally, remember to monitor your application and adjust the uWSGI worker count based on your actual traffic patterns.

D

Danila Vershinin

Founder & Lead Engineer

NGINX configuration and optimizationLinux system administrationWeb performance engineering

10+ years NGINX experience • Maintainer of GetPageSpeed RPM repository • Contributor to open-source NGINX modules

Leave a Reply

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

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

This site uses Akismet to reduce spam. Learn how your comment data is processed.