Deployment guide

uWSGI

Overview

uWSGI is a fast application server for hosting Python WSGI web applications. It runs your app in a supervised pool of worker processes, load balances requests across them, restarts crashed workers automatically, and exposes a live stats interface. nginx fronts it over a local unix socket.

This SecureAnyCloud image builds a pinned uWSGI 2.0.31 from the official source, with the Python 3 (WSGI) plugin compiled in. It runs uWSGI in Emperor mode, so one node can host one or many apps, each as an isolated vassal. uWSGI has no admin console or login, so the image ships a small Python WSGI demo app at / that proves the server executes code on every request.

What you get:

  • uWSGI 2.0.31, the latest stable release (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 plain-WSGI (PEP 3333) Python demo app at /, served by uWSGI over a unix socket and fronted by nginx on :80
  • A live server timestamp and worker PID, computed in Python on every request, so you can see uWSGI executing real code rather than serving a static file
  • A uWSGI stats server bound to loopback only (127.0.0.1:9191) for live worker inspection
  • uwsgi-firstboot.service, a systemd oneshot that starts the Emperor and nginx, confirms the demo app answers, and writes a non-secret endpoints notes file
  • Secure defaults: the stats server is never exposed off-box, server_tokens off is set, and there is no admin login surface and no baked credential
  • nginx on :80 as the only public entry point
  • An Ubuntu 24.04 LTS base with the latest security patches applied at build time
  • The Azure Linux Agent for cloud integration and SSH key injection
  • 24/7 SecureAnyCloud support with a guaranteed 24-hour response SLA

Prerequisites

You need an active Azure subscription, an SSH public key, and a VNet with a subnet in the target region. Your subscription must also be subscribed to the uWSGI listing on Azure Marketplace.

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

1

Deploy from the Azure Portal

Search the Marketplace for uWSGI, select the SecureAnyCloud publisher, and click Create. In the network security group (NSG), allow TCP 22 (admin) and TCP 80 (HTTP) from your client networks. Also allow TCP 443 if you terminate TLS on the instance.

2

Deploy from the Azure CLI

RG="uwsgi-prod"; LOCATION="eastus"; VM_NAME="uwsgi-01" IMAGE_URN="sac1647283583153:uwsgi-ubuntu-24-04:default:latest" 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
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:uwsgi-ubuntu-24-04:default:latest
az vm create -g "$RG" --name "$VM_NAME" --image "$IMAGE_URN" \
  --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
3

Connect via SSH

ssh azureuser@<vm-ip>

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

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 '

Run these commands to check the version and services. 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 [::]:*
5

Request the Python WSGI demo app

uWSGI has no admin UI, so the demo app is the proof that the image works. 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 show that uWSGI executes the app on every request.

<!doctype html><html><head><meta charset='utf-8'><title>uWSGI on sac</title></head><body><h1>uWSGI (open source)</h1><p>This page is created 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. It is computed in Python on every request, so it cannot come from a cached or static file. This is 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.

6

Inspect live workers with the stats server

The stats server shows the worker processes uWSGI 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 restarts any that crash. The master respawns a replacement transparently, and the stats server reports the new PID.

7

Read the endpoint notes and vassal config

The first-boot service writes a non-secret notes file that documents the demo URL and how to deploy your own app. uWSGI has no credentials to rotate. A single Emperor vassal .ini defines the demo app. Print the notes file and list the vassals:

sudo cat /var/lib/sac/data/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
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:

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'

Next, give the app its own Emperor vassal. Copy the bundled vassal as a template, point it at your app, and place it in the vassals directory. The Emperor loads it automatically, with no restart:

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/sac-uwsgi vhost is a working reference. Then validate the configuration and reload nginx:

sudo nginx -t && sudo systemctl reload nginx

Run these commands against your own app on your own VM.

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/sac-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/sac/data/uwsgi-endpoints.notes (mode 0644)

Firstboot sentinel /var/lib/sac/data/uwsgi-firstboot.done

10

Manage 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 then performs a graceful vassal reload:

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

Security recommendations

  • Keep the stats server on loopback. It is already configured this way. Never bind 127.0.0.1:9191 to a public address.
  • Add TLS. Front nginx with Let's Encrypt/Certbot and open :443.
  • Restrict the NSG. For a private service, make :80 and :443 reachable only from your client networks.
  • Run apps as an unprivileged user. The demo app runs as uwsgiapp. Keep your own apps off root.
  • Replace the demo app. Remove /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.
12

Support and licensing

uWSGI is licensed under the GNU General Public License version 2 with a linking exception. The exception 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.

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

  • Email: sales@secureanycloud.com
  • Website: secureanycloud.com
  • Support hours: 24/7 with a guaranteed 24-hour response SLA

Deploy on Azure

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

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure

Launch uWSGI on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure