Running Multiple Apps on One Host with uWSGI Emperor Mode
A practical walk‑through of uWSGI Emperor mode: set up a master process, add vassal configs, watch live reloads, and avoid common pitfalls.
11 Dec 2025, 08:03 UTC

The problem: many apps, one server
Production boxes often host several Python web services — API, admin panel, background worker UI — each with its own dependencies, worker count, and socket. Running a separate uWSGI master for every service wastes memory and makes rolling updates fragile. You need a single supervisor that can start, stop, and reload each app independently while keeping the whole stack observable.
How Emperor mode solves it
uWSGI’s Emperor is a master process that watches a directory of vassal configuration files. When a file appears, changes, or disappears, the Emperor spawns, reloads, or terminates the corresponding vassal instance. Each vassal runs as a full uWSGI process tree (master + workers) with its own socket, limits, and plugins, giving you isolation without multiple system‑level service units.
- Lazy loading – vassals start only when their config file exists.
- Cheap mode – idle vassals can drop workers to zero, freeing RAM.
- Mules – background tasks run inside a vassal without affecting request workers.
- Aggregated stats – the Emperor exposes a single stats socket that reports per‑vassal metrics.
Worked example: two Flask apps on the same host
Assume a Debian‑based server with a dedicated uwsgi user. The Emperor will run as that user and watch /etc/uwsgi/vassals.
1. Create the vassal directory
sudo mkdir -p /etc/uwsgi/vassals
sudo chown uwsgi:uwsgi /etc/uwsgi/vassals
Run the commands as root (or via sudo). The uwsgi user must have write access so the Emperor can create pid files and sockets.
2. Emperor configuration (systemd unit)
[Unit]
Description=uWSGI Emperor
After=network.target
[Service]
ExecStart=/usr/local/bin/uwsgi --emperor /etc/uwsgi/vassals \
--emperor-stats 127.0.0.1:9191 \
--uid uwsgi --gid uwsgi \
--logto /var/log/uwsgi/emperor.log
Restart=on-failure
User=uwsgi
Group=uwsgi
[Install]
WantedBy=multi-user.target
Save as /etc/systemd/system/uwsgi-emperor.service. The --emperor-stats flag enables the aggregated stats server on port 9191 (localhost only).
3. Vassal for App 1 (API)
; /etc/uwsgi/vassals/api.ini
[uwsgi]
chdir = /var/www/api
module = wsgi:app
master = true
processes = 4
socket = /run/uwsgi/api.sock
chmod-socket = 660
vacuum = true
lazy-apps = true
cheap = true
idle = 60
stats = /run/uwsgi/api-stats.sock
Key options: lazy-apps loads the app only after fork, cheap + idle drops workers after 60 s of inactivity.
4. Vassal for App 2 (Admin)
; /etc/uwsgi/vassals/admin.ini
[uwsgi]
chdir = /var/www/admin
module = wsgi:app
master = true
processes = 2
socket = /run/uwsgi/admin.sock
chmod-socket = 660
vacuum = true
stats = /run/uwsgi/admin-stats.sock
Both files live in the watched directory. The Emperor picks them up automatically.
5. Start and test
sudo systemctl daemon-reload
sudo systemctl enable --now uwsgi-emperor
Check the Emperor log:
sudo journalctl -u uwsgi-emperor -f
You should see lines like *** Starting vassal api.ini *** and *** Starting vassal admin.ini ***.
6. Live reload demonstration
Edit /etc/uwsgi/vassals/api.ini (e.g., change processes = 4 to processes = 6) and save. The Emperor logs a reload:
*** Reloading vassal api.ini ***
No Emperor restart required; only the API vassal recycles its workers.
7. Verify per‑vassal stats
curl -s http://127.0.0.1:9191 | jq .
The JSON payload contains an object per vassal with workers, requests, listen_queue, etc., confirming isolation.
Trade‑offs and gotchas
- Restart loops: A syntax error or missing module in a vassal ini makes the Emperor repeatedly respawn it, spiking CPU and filling logs. Validate configs with
uwsgi --ini /etc/uwsgi/vassals/api.ini --dry-runbefore dropping them in the directory. - Global options not inherited: Settings such as
limit-as,harakiri, ormaster-fifoset on the Emperor command line do not propagate to vassals. Duplicate them in each vassal file or generate vassal configs from a template. - Socket permissions: The Emperor runs as
uwsgi; vassal sockets inherit that ownership. Ensure your reverse proxy (nginx, haproxy) runs in a group that can read/write those sockets. - Single point of failure: If the Emperor dies, all vassals stop. Mitigate with a systemd
Restart=on-failureand, for higher availability, run a second Emperor on a standby host with shared config via a configuration management tool.
Verify and monitor in production
- Confirm the Emperor process is alive:
systemctl status uwsgi-emperor. - Poll the stats endpoint every 30 s (Prometheus exporter, Telegraf, or a simple cron) and alert on
workers == 0for a vassal that should be hot. - Log rotation: the Emperor writes to
/var/log/uwsgi/emperor.log; configurelogrotateto avoid disk pressure.
Next steps
Add a third vassal for a background worker (using mule directives) or enable --emperor-nofollow to ignore symlinks for stricter security. Template your vassal files with Ansible or Jinja2 so shared limits stay consistent. With the Emperor in place you gain atomic per‑app deployments, centralized observability, and a single service to manage — without sacrificing the isolation each application deserves.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.