Application Servers Azure

uWSGI on Ubuntu 24.04 on Azure User Guide

| Product: uWSGI on Ubuntu 24.04 LTS on Azure

Overview

This guide covers the deployment and configuration of uWSGI on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. uWSGI is a fast, production grade application server for hosting Python WSGI web applications. It runs your app in a supervised pool of worker processes, load balances requests across them, automatically restarts crashed workers, and exposes a live stats interface for visibility into worker health and request load — all fronted by nginx over a fast local unix socket.

The image builds a pinned uWSGI 2.0.31 (the latest stable release) from the official source, with the Python 3 (WSGI) plugin compiled in, and runs it in Emperor mode so a single node can host one or many apps, each as an isolated vassal. uWSGI is an application server with no admin console or login — there is nothing to authenticate to — so this image ships a small Python WSGI demo app pre-deployed and served through uWSGI, proving the server is genuinely executing code on every request:

  • a plain-WSGI (PEP 3333) Python app at / that renders a live server timestamp and the worker PID, computed in Python on every request

The demo app renders a live epoch and worker PID that change per request, so you can see uWSGI executing real code rather than serving a static file.

What is included:

  • uWSGI 2.0.31 (open source, GPL-2.0 with a linking exception) built from the official PyPI source with the Python 3 WSGI plugin embedded

  • The uWSGI Emperor running as a systemd service (uwsgi-emperor.service), supervising a vassal that spawns a master plus two workers

  • A Python WSGI demo app at /, served by uWSGI over a unix socket and fronted by nginx on :80

  • A uWSGI stats server bound to loopback only (127.0.0.1:9191) for live worker inspection

  • uwsgi-firstboot.service systemd oneshot that starts the Emperor and nginx, confirms the demo app answers, and writes a non-secret endpoints notes file

  • Secure by default: the stats server is never exposed off-box, server_tokens off, there is no admin login surface and no baked credential

  • nginx on :80 is the only public entry point

  • Ubuntu 24.04 LTS base with latest security patches applied at build time

  • Azure Linux Agent for seamless cloud integration and SSH key injection

  • 24/7 cloudimg support with guaranteed 24 hour response SLA

Prerequisites

  • Active Azure subscription, SSH public key, VNet + subnet in target region

  • Subscription to the uWSGI listing on Azure Marketplace

Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) for development and light application workloads. Higher concurrency or CPU-bound apps should use Standard_D4s_v5 (4 vCPU, 16 GB RAM) or larger.

Step 1: Deploy from the Azure Portal

Search uWSGI in Marketplace, select the cloudimg publisher, click Create. NSG rules: TCP 22 (admin) and TCP 80 (HTTP) from your client networks. Add TCP 443 if you terminate TLS on the instance.

Step 2: Deploy from the Azure CLI

RG="uwsgi-prod"; LOCATION="eastus"; VM_NAME="uwsgi-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/uwsgi-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name uwsgi-vnet --address-prefix 10.100.0.0/16 --subnet-name uwsgi-subnet --subnet-prefix 10.100.1.0/24
az network nsg create -g "$RG" --name uwsgi-nsg
az network nsg rule create -g "$RG" --nsg-name uwsgi-nsg --name allow-ssh --priority 100 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name uwsgi-nsg --name allow-http --priority 110 \
  --destination-port-ranges 80 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
  --size Standard_B2s --storage-sku StandardSSD_LRS \
  --admin-username azureuser --ssh-key-values "$SSH_KEY" \
  --vnet-name uwsgi-vnet --subnet uwsgi-subnet --nsg uwsgi-nsg --public-ip-sku Standard

Step 3: Connect via SSH

ssh azureuser@<vm-ip>

uwsgi-emperor.service and nginx.service start automatically on first boot — there is no admin account to set up, because uWSGI has no login.

Step 4: Verify uWSGI and nginx

uwsgi --version
sudo systemctl is-active uwsgi-emperor.service
sudo systemctl is-active nginx.service
sudo ss -tln | grep ':80 '

Expected output — the pinned uWSGI is present, both services are active, and nginx is listening on :80:

2.0.31
active
active
LISTEN 0      511          0.0.0.0:80        0.0.0.0:*
LISTEN 0      511             [::]:80           [::]:*

uwsgi --version reports 2.0.31, uwsgi-emperor.service and nginx.service are both active, and nginx is listening on port 80

Step 5: Request the Python WSGI Demo App

uWSGI has no admin UI, so the value-proof for this image is the app it serves. Request the demo app at / twice, one second apart:

curl -s http://127.0.0.1/
sleep 1
curl -s http://127.0.0.1/

Expected output — the epoch and Worker PID prove uWSGI is executing the app on every request:

<!doctype html><html><head><meta charset='utf-8'><title>uWSGI on cloudimg</title></head><body><h1>uWSGI (open source)</h1><p>This page is generated by a Python WSGI application running under uWSGI, fronted by nginx over a unix socket.</p><p>Server time: 2026-07-20T06:42:49Z epoch=1784529769</p><p>Worker PID: 7320</p><p>The epoch and PID above are computed in Python on every request &mdash; this is a live application, not a static file.</p></body></html>

The epoch value increments between calls — an in-process, per-request value computed in Python that cannot come from a cached or static file. This is exactly the work an application server does that a plain static web server cannot. The request travels nginx :80 → uwsgi_pass → unix socket /run/uwsgi/demo.sock → a uWSGI worker → your Python callable.

curl of the demo app at / issued twice one second apart, each returning HTTP 200 with a live server timestamp, an incrementing epoch and the worker PID, proving genuine per-request Python execution through uWSGI

Step 6: Inspect Live Workers with the Stats Server

uWSGI's stats server is its operational window into the worker processes it manages. It is bound to loopback only (127.0.0.1:9191) and never exposed off-box. Warm the app, then read the stats socket:

curl -s -o /dev/null http://127.0.0.1/
python3 - <<'PYEOF'
import socket, json
s = socket.create_connection(('127.0.0.1', 9191), timeout=5); s.settimeout(2)
chunks = []
try:
    while True:
        c = s.recv(65536)
        if not c:
            break
        chunks.append(c)
except socket.timeout:
    pass
d = json.loads(b''.join(chunks))
print('uWSGI', d['version'], 'master pid', d['pid'])
for w in d['workers']:
    print('worker id=%s pid=%s status=%s requests=%s' % (w['id'], w['pid'], w['status'], w['requests']))
PYEOF

Expected output — the master plus two live workers, each with its PID, status and request count:

uWSGI 2.0.31 master pid 7319
worker id=1 pid=7320 status=idle requests=2
worker id=2 pid=7321 status=idle requests=3

uWSGI spawns these workers under the Emperor and automatically restarts any that crash — the master respawns a replacement transparently, and the stats server reflects the new PID.

the uWSGI stats server read from the loopback socket showing uWSGI 2.0.31, the master PID, and two live worker processes with their PIDs, idle status and request counts

Step 7: Endpoint Notes and the Vassal Config

The firstboot service writes a non-secret notes file documenting the demo URL and how to deploy your own app (uWSGI has no credentials to rotate). The demo app is defined by a single Emperor vassal .ini:

sudo cat /var/lib/cloudimg/uwsgi-endpoints.notes
ls /etc/uwsgi-emperor/vassals/

Expected output — the notes file and the demo vassal:

# uWSGI — Endpoint Notes
# Generated on first boot by uwsgi-firstboot.service
#
UWSGI_DEMO_URL=http://<vm-ip>/
UWSGI_VERSION=2.0.31
UWSGI_APP_ROOT=/opt/uwsgi-demo
UWSGI_VASSAL=/etc/uwsgi-emperor/vassals/demo.ini
UWSGI_SOCKET=/run/uwsgi/demo.sock
UWSGI_STATS=127.0.0.1:9191 (loopback only)
demo.ini

the uWSGI endpoints notes file documenting the demo URL, version, app root, vassal, socket and loopback stats address, alongside a listing of the Emperor vassals directory showing demo.ini

Step 8: Deploy Your Own WSGI App

uWSGI serves any Python WSGI application — Flask, Django, FastAPI (via a WSGI adapter), or a plain callable. Copy your app to the VM and give it its own Emperor vassal:

scp -r ./myapp azureuser@<vm-ip>:/tmp/myapp
ssh azureuser@<vm-ip> 'sudo mv /tmp/myapp /opt/myapp && sudo chown -R uwsgiapp:uwsgiapp /opt/myapp'

Then copy the bundled vassal as a template, point it at your app, and drop it into the vassals directory — the Emperor auto-loads it, no restart needed:

sudo cp /etc/uwsgi-emperor/vassals/demo.ini /etc/uwsgi-emperor/vassals/myapp.ini
sudo sed -i 's#/opt/uwsgi-demo#/opt/myapp#; s#demo.sock#myapp.sock#; s#9191#9192#' /etc/uwsgi-emperor/vassals/myapp.ini

Add a matching nginx location with uwsgi_pass unix:/run/uwsgi/myapp.sock; (the bundled /etc/nginx/sites-available/cloudimg-uwsgi vhost is a working reference), then validate and reload:

sudo nginx -t && sudo systemctl reload nginx

(Run these against your own app on your own VM — not executed as part of this guide's verification.)

Step 9: Server Components

Component Path
uWSGI binary (pinned 2.0.31) /usr/local/bin/uwsgi
Emperor systemd unit /etc/systemd/system/uwsgi-emperor.service
Demo app vassal /etc/uwsgi-emperor/vassals/demo.ini
Python WSGI demo app /opt/uwsgi-demo/app.py
App socket /run/uwsgi/demo.sock
Stats server 127.0.0.1:9191 (loopback only)
nginx vhost /etc/nginx/sites-available/cloudimg-uwsgi
Application user uwsgiapp (unprivileged system user)
Logs /var/log/uwsgi-emperor.log + systemd journal + /var/log/nginx/
Firstboot script /usr/local/sbin/uwsgi-firstboot.sh
Firstboot service /etc/systemd/system/uwsgi-firstboot.service
Endpoint notes /var/lib/cloudimg/uwsgi-endpoints.notes (mode 0644)
Firstboot sentinel /var/lib/cloudimg/uwsgi-firstboot.done

Step 10: Managing uWSGI and nginx

sudo systemctl status uwsgi-emperor.service --no-pager
sudo systemctl status nginx.service --no-pager
sudo systemctl reload nginx.service

To pick up new application code without a full restart, touch the vassal — the Emperor performs a graceful vassal reload:

sudo touch /etc/uwsgi-emperor/vassals/demo.ini

Step 11: Security Recommendations

  • Keep the stats server on loopback (already configured) — never bind 127.0.0.1:9191 to a public address

  • Add TLS by fronting nginx with Let's Encrypt/Certbot and opening :443

  • Restrict the NSG so :80/:443 are reachable only from your client networks if this is an internal service

  • Run apps as an unprivileged user — the demo app runs as uwsgiapp; keep your own apps off root

  • Remove or replace the demo app (/opt/uwsgi-demo/ and its vassal) once you deploy your own application

  • Patch the OS monthly with sudo apt-get update && sudo apt-get upgrade && sudo reboot

Step 12: Support and Licensing

uWSGI is licensed under the GNU General Public License version 2 with a linking exception, which explicitly permits linking and serving your own applications — including closed-source ones — without restriction from the uWSGI license. There is no per-CPU or per-deployment fee for uWSGI itself.

cloudimg provides commercial support for this image separately from the upstream project.

  • Email: support@cloudimg.co.uk
  • Website: www.cloudimg.co.uk
  • Support hours: 24/7 with guaranteed 24 hour response SLA

Deploy on Azure

Launch uWSGI on Ubuntu 24.04 with 24/7 support from cloudimg.

View on Marketplace

Need Help?

Our support team is available 24/7.

support@cloudimg.co.uk