Managing Multiple Python Apps with uWSGI Emperor Mode: A Practical Guide
Deploy and maintain several Python web applications on a single server using uWSGI Emperor mode. Learn how to set up the emperor, configure vassals, verify operation, and recover from failures with minimal downtime.
20 Aug 2025, 22:30 UTC

Why Use uWSGI Emperor Mode?
When a server hosts multiple Python web applications, managing each process manually becomes tedious. uWSGI’s Emperor mode automatically monitors a directory of vassal configuration files, starts a separate uWSGI instance for each app, and restarts or reloads them on file changes. This eliminates the need for separate systemd units or manual supervisor scripts.
Desired Outcome
Run a single uWSGI Emperor process that spawns, monitors, and reloads independent vassals for each application. Each vassal should bind to its own socket, log independently, and be recoverable with simple file touches or service restarts.
Prerequisites
- Operating system: Linux (Debian/Ubuntu, CentOS/RHEL, or similar).
Requires root privileges for low‑port binding or privilege dropping viauidandgidin vassal configs. - Python 3.8+ and
pipinstalled. - uWSGI 2.0+ installed globally or per‑app virtualenv. Common installation:
pip install uwsgiorapt-get install uwsgi. - Directory structure:
mkdir -p /etc/uwsgi/vassals mkdir -p /var/log/uwsgi - One virtualenv per application, e.g.,
/srv/app1/venvand/srv/app2/venv.
Step 1: Create the Emperor Configuration
Place a single master config file, e.g. /etc/uwsgi/emperor.ini:
[uwsgi]
# Emperor watches this directory for vassal .ini files
emperor = /etc/uwsgi/vassals
# Log all emperor activity to a dedicated file
logto = /var/log/uwsgi/emperor.log
# Optional: enable stats for monitoring
stats = 127.0.0.1:9191
# Run the emperor as a daemon
daemonize = true
# If you need to bind to ports < 1024, run as root and drop privileges in vassals
uid = www-data
gid = www-data
Run the emperor from the command line or via systemd:
# Direct launch
uwsgi --ini /etc/uwsgi/emperor.ini
# Systemd service example (uwsgi-emperor.service)
[Unit]
Description=uWSGI Emperor
After=network.target
[Service]
ExecStart=/usr/local/bin/uwsgi --ini /etc/uwsgi/emperor.ini
Restart=always
[Install]
WantedBy=multi-user.target
Step 2: Configure Individual Vassals
Each vassal gets its own .ini file inside /etc/uwsgi/vassals. Example for two apps:
# /etc/uwsgi/vassals/app1.ini
[uwsgi]
module = app1.wsgi:application
master = true
processes = 4
socket = /run/uwsgi/app1.sock
chmod-socket = 660
vacuum = true
virtualenv = /srv/app1/venv
uid = www-data
gid = www-data
# Optional performance tweaks
lazy-apps = true
cheaper = 1
cheaper-initial = 1
cheaper-step = 1
# /etc/uwsgi/vassals/app2.ini
[uwsgi]
module = app2.wsgi:application
master = true
processes = 2
socket = /run/uwsgi/app2.sock
chmod-socket = 660
vacuum = true
virtualenv = /srv/app2/venv
uid = www-data
gid = www-data
Notice each vassal uses a distinct Unix socket under /run/uwsgi to avoid binding conflicts. The vacuum flag cleans up the socket on shutdown.
Step 3: Launch the Emperor
Start the emperor process (if not already running via systemd):
# If using systemd
systemctl start uwsgi-emperor
# Verify service status
systemctl status uwsgi-emperor
Or launch manually for debugging:
uwsgi --ini /etc/uwsgi/emperor.ini
Expected Checks
- Process Verification
Runps -ef | grep uwsgito confirm a single emperor process and multiple vassal processes. Example output:root 1201 1 0 10:15 ? 00:00:00 /usr/local/bin/uwsgi --ini /etc/uwsgi/emperor.ini www-data 1205 1201 0 10:15 ? 00:00:00 /usr/local/bin/uwsgi --socket /run/uwsgi/app1.sock ... - Log Inspection
Check/var/log/uwsgi/emperor.logfor messages like:2019-01-01 10:15:00 INFO [emperor] Starting vassal /etc/uwsgi/vassals/app1.ini 2019-01-01 10:15:01 INFO [app1] Listening on /run/uwsgi/app1.sock - Socket Binding
Verify each socket is listening:ss -ltnp | grep app1 unix 2 0 0 0 0 0 0 0 /run/uwsgi/app1.sock (LISTEN) 1205/uwsgi - HTTP Health Check
Usecurlagainst the socket viaunix://or a reverse proxy. Example withuwsgi_passin Nginx:
Expect a 200 OK response and a JSON body likecurl --unix-socket /run/uwsgi/app1.sock http://localhost/health{"status":"ok"}. - Stats Server
Connect to127.0.0.1:9191and requestGET /to view real‑time metrics. A simpletelnetornccan be used:
Output includes process counts, request rates, and memory usage.telnet 127.0.0.1 9191 GET / HTTP/1.0
Recovery Options
When a vassal crashes or misbehaves, follow these steps:
- Inspect the vassal log – each vassal writes to
/var/log/uwsgi/.logby default. Look for tracebacks or import errors. - Fix the issue – edit the app code or the
.inifile. - Trigger a reload – the emperor watches file timestamps. Touch the config to force a restart:
The emperor logstouch /etc/uwsgi/vassals/app1.iniReloading vassal app1.iniand spawns a new process. - Restart the emperor – if many vassals fail or you need a clean slate:
This stops all vassals, reloads configs, and starts fresh.systemctl restart uwsgi-emperor
Optional Performance Tweaks
- Lazy‑apps – delay module import until the first request, reducing startup time.
- Cheaper mode – automatically scale the number of worker processes based on load. Add
cheaper = 1and related options to the vassal config. - Drop privileges – set
uidandgidin each vassal to avoid running as root.
Limitations & Gotchas
- All vassals share the same uWSGI binary; incompatible module requirements can cause import errors.
- Using sockets under
/run/uwsgirequires a systemd unit to create the directory with correct permissions before the emperor starts. - Root privilege is needed only for binding to ports < 1024; using Unix sockets sidesteps this requirement.
- When using
daemonize = truein the emperor config, be sure log files are writable by the emperor user.
Conclusion
uWSGI Emperor mode offers a lightweight, declarative way to host multiple Python applications on a single server. By organizing vassal configs in a directory, the emperor handles process lifecycle, restarts, and health monitoring automatically. The steps above provide a reproducible path from installation to verification, with clear recovery actions to keep the deployment resilient.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.