uWSGI Application Server on AWS User Guide
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
:80front door, proxying to uWSGI over a unix socket withuwsgi_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.servicesystemd 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; nginxserver_tokens off; the Emperor runs no privileged vassal -
nginx on
:80is 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.

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.

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.

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.

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:9191and 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/: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/app.pyand 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