This article is part of the Homelab Self-Hosting Guide – the curated learning path for your own homelab.
Why not just Gunicorn?
When you want to put a Django app into production, you reflexively reach for Gunicorn or uWSGI – both have been the standard stack behind every Django deployment for years. So did I, for a long time.
But after the third project where I had to restart the service for every configuration change, and the endless Nginx-as-reverse-proxy-in-front-of-Gunicorn setup, I came across Nginx Unit. The appeal: one component instead of two, live-configurable via REST API, and without the typical reload downtime.
Ad · Affiliate link – if you buy through it, I may earn a commission. It doesn’t change the price for you.
Nginx Unit is an application server from the makers of Nginx that serves Python (WSGI/ASGI), Go, PHP, Ruby, Java and more in a single process. The entire configuration runs through a REST API – no editing config files, no service restart. Changes are applied atomically, without dropping a single request.
Installation
On Debian/Ubuntu, Unit is in the official repos. Don't forget the Python module:
sudo apt update
sudo apt install unit unit-dev unit-python3After installation the Unit daemon runs automatically. The API is reachable via a Unix socket – no open port, no auth problem.
The JSON configuration
Instead of classic config files, Unit works with a JSON payload. Here is the minimal configuration for a Django project:
{
"listeners": {
"*:8000": {
"pass": "applications/django"
}
},
"applications": {
"django": {
"type": "python 3",
"path": "/var/www/myproject/",
"module": "myproject.wsgi",
"environment": {
"DJANGO_SETTINGS_MODULE": "myproject.settings"
}
}
}
}The structure is self-explanatory: a listener on port 8000 forwards everything to the Django app. path points to the project directory, module to the WSGI module. That's all you need to get started.
Applying the configuration – without a restart
The configuration is sent to the Unit socket via curl. This is the moment where Unit plays its biggest advantage – the change takes effect immediately, without interrupting the running service:
sudo curl -X PUT --data-binary @config.json \
--unix-socket /var/run/control.unit.sock \
http://localhost/config/You can change worker processes, listener ports or even the Python version while it is running – just adjust the JSON and PUT it again. Unit applies the changes atomically. No systemctl reload, no risk of dropped requests.
After the PUT, the Django app is immediately reachable on port 8000. For production use you typically still put a reverse proxy like Nginx or Traefik in front – but Unit itself can also serve static files directly.
What I left out
This article shows the minimal setup. The following is deliberately not covered:
- Docker deployment. Unit has an official Docker image – that would be an article of its own.
- Serving static files directly via Unit. It works, but for large projects Whitenoise or a CDN is usually the better solution.
- ASGI instead of WSGI. Unit can do ASGI too (for Channels, async views) – it needs a slightly different JSON config.
- systemd hardening. In production the Unit process should be locked down via systemd (
ProtectSystem,NoNewPrivilegesetc.).
Conclusion & outlook
Nginx Unit is, for me, the cleanest way to put a Django app into production once you are tired of the Gunicorn-plus-Nginx double pack. One component, JSON config over REST, live reconfiguration without downtime. For small to mid-sized projects the setup is done in 15 minutes.
If you already run a reverse proxy like Traefik or Nginx Proxy Manager in your homelab, just put Unit behind it and you have a complete production setup.
Ad · Affiliate link – if you buy through it, I may earn a commission. It doesn’t change the price for you.