uWSGI on Ubuntu 24.04 on Azure User Guide
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.servicesystemd 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
:80is 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 [::]:*

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 — 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.

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.

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

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:9191to a public address -
Add TLS by fronting nginx with Let's Encrypt/Certbot and opening
:443 -
Restrict the NSG so
:80/:443are 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