Application Servers AWS

uWSGI Application Server on AWS User Guide

| Product: uWSGI Application Server on AWS

Overview

This guide covers the deployment and operation of uWSGI on AWS using cloudimg Marketplace AMIs. uWSGI is a mature, production-grade application server for hosting Python WSGI web applications (and, through its plugins, other languages). 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 for visibility into worker health and request load. Running in Emperor mode it can host one or many apps on a single node, each as an isolated vassal, with nginx as the front door over a fast unix socket.

uWSGI is infrastructure, not a finished end-user application. This image gives you a wired-up, hardened application server that you drop your own WSGI/Python app into; it does not ship a turnkey app and there is no admin console or login. To prove the server is working the moment the instance boots, the image ships a tiny Python WSGI demo app as a health and example endpoint:

  • a plain-WSGI Python app at / that renders a live server timestamp and the worker PID, served by uWSGI through nginx over the unix socket /run/uwsgi/demo.sock

The demo app renders a live epoch and worker PID that change on every request, so you can see uWSGI executing real Python code rather than serving a static file. Replace it with your own Flask, Django or FastAPI application.

What is included:

  • uWSGI 2.0.31 (open source, GPL-2.0 with a linking exception that explicitly permits serving your own closed-source apps), built from the official PyPI source with the Python 3 WSGI plugin embedded

  • nginx as the :80 front door, proxying to uWSGI over a unix socket with uwsgi_pass

  • A plain-WSGI Python demo app served through the socket, proving genuine per-request execution

  • A dedicated EBS application data volume mounted at /opt/uwsgi-demo, so your app code and vassal configs live on their own independently-resizable disk

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

  • Secure by default: the uWSGI stats server binds to loopback only (127.0.0.1:9191) and is never exposed off-box; nginx server_tokens off; the Emperor runs no privileged vassal

  • nginx on :80 is the only public entry point; there is no admin login surface and no baked credential

  • Ubuntu 24.04 LTS base with latest security patches applied at build time; the AWS SSM agent installed for Systems Manager integration

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

A note on the upstream project: uWSGI is a stable, widely deployed application server in maintenance mode upstream (security and correctness fixes rather than active new-feature development). cloudimg ships the latest stable release and keeps the image patched.

Prerequisites

  • An AWS account subscribed to the uWSGI listing on AWS Marketplace

  • An EC2 key pair, a VPC with a public subnet, and a security group allowing inbound TCP 22 (SSH) and TCP 80 (HTTP) from your client networks

Recommended instance type: m5.large (2 vCPU, 8 GB RAM) for development and light-to-moderate application workloads. Higher concurrency or CPU-bound apps should use m5.xlarge or larger.

Connecting to your instance

Each OS variant of this listing has its own default SSH login user. Use the row matching the AMI variant you launched.

OS variant SSH login user Example
Ubuntu 24.04 LTS ubuntu ssh -i your-key.pem ubuntu@<instance-public-ip>

Step 1: Launch from the AWS Console

In the AWS Marketplace or the EC2 console, subscribe to the uWSGI listing and choose Launch. Select an m5.large (or larger) instance type, your key pair, and a security group that allows:

  • TCP 22 (SSH) from your management network
  • TCP 80 (HTTP) from your client networks

Add TCP 443 if you plan to terminate TLS on the instance.

Step 2: Launch from the AWS CLI

AMI_ID=<the-uwsgi-ami-id>
aws ec2 run-instances \
  --image-id "$AMI_ID" \
  --instance-type m5.large \
  --key-name your-key \
  --security-group-ids sg-xxxxxxxx \
  --subnet-id subnet-xxxxxxxx \
  --associate-public-ip-address \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=uwsgi-01}]'

Step 3: Connect via SSH

ssh -i your-key.pem ubuntu@<public-ip>

uwsgi-emperor.service, nginx.service and uwsgi-firstboot.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

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

Expected output. uWSGI reports the pinned version, 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            [::]:*

nginx proxies port 80 to uWSGI over the unix socket /run/uwsgi/demo.sock; the Emperor supervises the vassal that serves the app.

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 demo app it serves. Request the app at / and confirm it returns HTTP 200 through nginx to the uWSGI socket:

curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1/
curl -s http://127.0.0.1/

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

HTTP 200
<!doctype html><html><head><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-23T16:35:41Z epoch=1784824541</p><p>Worker PID: 4263</p></body></html>

Request it twice a couple of seconds apart and the epoch increments, an in-process, per-request value 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:

curl -s http://127.0.0.1/ | grep -oE 'epoch=[0-9]+'
sleep 2
curl -s http://127.0.0.1/ | grep -oE 'epoch=[0-9]+'

Expected output. Two different epoch values:

epoch=1784824558
epoch=1784824560

You can also open http://<instance-public-ip>/ in a browser.

curl of the demo app returning HTTP 200 with a live server timestamp, epoch and worker PID, and two requests two seconds apart showing the epoch change, proving genuine per-request Python execution

Step 6: Inspect Live Workers with the Emperor and Stats Server

The uWSGI Emperor supervises the demo vassal, which runs a master and a small pool of worker processes. Inspect the Emperor's process tree, then read the loopback-only stats server:

sudo systemctl status uwsgi-emperor.service --no-pager
cat < /dev/tcp/127.0.0.1/9191

Expected output. The Emperor is active (running) and supervises the vassal master plus worker processes; the stats server (JSON) reports the live workers and their PIDs:

● uwsgi-emperor.service - uWSGI Emperor (cloudimg)
     Active: active (running)   Main PID: 4256 (uwsgi)
     CGroup: /system.slice/uwsgi-emperor.service
       4256 uwsgi --master --emperor /etc/uwsgi-emperor/vassals
       4260 uwsgi --ini demo.ini
       4262 uwsgi --ini demo.ini
       4263 uwsgi --ini demo.ini

{"version":"2.0.31", ... "workers":[{"id":1,"pid":4262,"status":"idle", ...},{"id":2,"pid":4263,"status":"idle", ...}], "sockets":[{"name":"/run/uwsgi/demo.sock","proto":"uwsgi", ...}]}

The stats server is bound to 127.0.0.1 only, so it is never reachable off-box. uWSGI respawns any worker that crashes.

systemctl status showing the uWSGI Emperor supervising the vassal master and two workers, and the loopback stats socket reporting the live worker PIDs and the demo unix socket

Step 7: Burst Test and Endpoint Notes

Every request is served by uWSGI. Send a 25-request burst and confirm each returns 200, then read the non-secret endpoints notes file the first-boot service wrote:

OK=0; for i in $(seq 1 25); do C=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/); [ "$C" = 200 ] && OK=$((OK+1)); done; echo "burst 200s: $OK/25"
sudo cat /var/lib/cloudimg/uwsgi-endpoints.notes

Expected output. All 25 requests return 200, and the notes file documents the demo URL and where to deploy your own app (uWSGI has no credentials to rotate):

burst 200s: 25/25
# uWSGI - Endpoint Notes
UWSGI_DEMO_URL=http://<instance-public-ip>/
UWSGI_VERSION=2.0.31
UWSGI_APP_ROOT=/opt/uwsgi-demo
UWSGI_VASSAL=/etc/uwsgi-emperor/vassals/demo.ini
UWSGI_STATS=127.0.0.1:9191 (loopback only)
DEPLOY_YOUR_OWN_APP=Put your WSGI app under /opt/uwsgi-demo/<name>/, copy the demo vassal, point wsgi-file/chdir/socket at it, then reload nginx.

a 25-request burst all returning 200, and the endpoints notes file documenting the demo URL, the uWSGI version, the app root, the vassal and the loopback stats server

Step 8: Deploy Your Own WSGI App

uWSGI serves any Python WSGI app, plus other languages via plugins. Your application tier lives on the dedicated EBS volume mounted at /opt/uwsgi-demo. The commands below are examples to run against your own app on your own instance; they are not part of this guide's verification. Copy your app onto the data volume:

$ scp -r -i your-key.pem ./myapp ubuntu@<public-ip>:/tmp/myapp
$ ssh -i your-key.pem ubuntu@<public-ip> 'sudo mv /tmp/myapp /opt/uwsgi-demo/myapp && sudo chown -R uwsgiapp:uwsgiapp /opt/uwsgi-demo/myapp'

Copy the demo vassal, point it at your app's WSGI callable using a new socket path (each vassal needs its own socket), and drop it into the vassals directory; the Emperor picks it up automatically:

$ sudo cp /etc/uwsgi-emperor/vassals/demo.ini /etc/uwsgi-emperor/vassals/myapp.ini
# edit chdir, wsgi-file, callable and socket=/run/uwsgi/myapp.sock in myapp.ini
$ sudo nginx -t && sudo systemctl reload nginx

Step 9: Server Components

Component Path
uWSGI binary /usr/local/bin/uwsgi
Emperor systemd unit /etc/systemd/system/uwsgi-emperor.service
Vassal directory /etc/uwsgi-emperor/vassals/
Demo app vassal /etc/uwsgi-emperor/vassals/demo.ini
Python WSGI demo app /opt/uwsgi-demo/app.py
Application data volume /opt/uwsgi-demo (dedicated EBS volume)
Unix 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)
Emperor log /var/log/uwsgi-emperor.log
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 nginx.service --no-pager
sudo systemctl status uwsgi-emperor.service --no-pager

The Emperor auto-loads, reloads and stops vassals as their .ini files appear, change or are removed in /etc/uwsgi-emperor/vassals/. To pick up new application code, touch the vassal or use uWSGI's own reload; to change the front door, edit the nginx vhost and sudo systemctl reload nginx.

Step 11: Security Recommendations

  • Keep the stats server on loopback (already configured). It is bound to 127.0.0.1:9191 and must never be exposed off-box

  • Add TLS by fronting nginx with Let's Encrypt/Certbot and opening :443, or terminate TLS at an Application Load Balancer with AWS Certificate Manager. No TLS private key is baked into the image

  • Restrict the security group 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/app.py 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 v2 with a linking exception, which explicitly permits linking and serving your own applications, including closed-source ones, without restriction. There is no per-CPU or per-deployment fee.

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 AWS

Launch uWSGI with 24/7 support from cloudimg.

View on Marketplace

Need Help?

Our support team is available 24/7.

support@cloudimg.co.uk