
In the past few days, I dove into NGINX Unit, a server developed by the NGINX team itself — designed to replace the traditional combo “NGINX + Gunicorn + uWSGI.”
The promise sounds great: dynamic configuration via API, support for multiple apps (Python, PHP, Go, etc.) in the same process, and even a reverse proxy for other containers.
But in practice, Unit can be full of surprises — and not always pleasant ones.
Here’s a summary of what I learned — including mistakes, fixes, and real code examples.
What is NGINX Unit
NGINX Unit is a modular application server created by the NGINX team, but with a very different philosophy:
It doesn’t use configuration files in the classic nginx.conf format.
All configuration is done through an HTTP API (or Unix socket). Everything can be updated in real time, without restarting the service.
You can, for example, add a new website, change the runtime language, or create a reverse proxy — all by sending a JSON POST request.
Running Unit with Docker
The Compose file for the Unit container looks like this YAML:
services:
unit:
image: nginx/unit:1.32.1
ports:
- "8080:80"
volumes:
- ./config:/docker-entrypoint.d
- ./sites:/var/www/sites
To apply a configuration, use the following command:
curl -X PUT --data-binary @config.json http://localhost:8080/config
Configuring Static Sites
In order to configure a site that serves only static content, you can use a JSON like this:
{
"listeners": {
"*:80": {
"pass": "routes/main"
}
},
"routes": {
"main": [
{
"match": {
"host": "site1.example.com"
},
"action": {
"share": "/var/www/sites/site1"
}
}
]
}
}
💡 Tip: The share path must exist inside the container, so make sure to mount your volume correctly in docker-compose.yml.
Networking Details
Unit doesn’t recognize container names (like app:8000) because it doesn’t use Docker’s internal DNS.
You’ll need to use either a fixed IP (i.e., the app’s IP configured in your Compose file) or an alias added via extra_hosts.
The JSON applied to Unit will have a similar structure, except for this section:
"routes": {
"main": [
{
"match": {
"host": "deployer.linkei.app.br"
},
"action": {
"proxy": "http://172.28.0.11:8000",
"rewrite": "$uri"
}
}
]
}
⚠️ Important: The rewrite field must be a string, not an object.
Common Errors and Fixes
A very short list of errors and respective solutions.
| ERROR | CAUSE | SOLUTION |
|---|---|---|
403 Forbidden | Unit doesn’t has the permissions to access the share directory | Adjust the permissions(chmod -R a+rX /var/www/sites) |
Request "pass" points to invalid location | Reference error in the "pass" field | Use "pass": "routes/main", not "routes/main/" |
“connection reset“ | Unit can’t reach out the backend | Use a fixed IP or extra_hosts |
“types value must be string or array“ | Incorrect JSON when defining MIME types | Use "types": ["text/html", "text/css", ...] |
Conclusion
NGINX Unit is a powerful — yet unusual — tool. It requires some experimentation and a careful read of the official documentation, especially because tiny JSON details can cause big headaches.
But once you understand it, it offers incredible flexibility:
You can deploy and update static sites, APIs, and apps in real time, via API — without restarting anything.
It’s ideal for those building automated deployment platforms.