--- title: Make Nginx Unit controllable from non-root user date: 2025-05-09 21:11:38.031033 UTC --- Recently I heard about [Nginx Unit](https://unit.nginx.org/), a piece of software which lets you run Python web application on production with less components. It's like you let Nginx run your Python code, no longer Nginx - Gunicorn separation. So I installed it and learned how to use, but the default setup is not convenient: You have to switch to `root` user too often. So I share extra steps of setup to save you from it. Nginx Unit is not configured via file like Nginx. It is done via HTTP API, which is nice because Nginx Unit can get update with new configuration without restarting, although I don't know what is the benefit over using [SIGHUP](https://en.wikipedia.org/wiki/SIGHUP) signal to trigger rereading. The API communication is done via Unix domain socket, leveraging Linux authentication for restricting access, which is very reasonable because we don't have to remember port, username and password. But the issue is that, the default setup makes the socket writable by root only. ![Unit control socket](https://quan-images.b-cdn.net/blogs/imgur/2026/Trh7keY.png) The annoyance come from the combination of these factors: - Very few CLI HTTP clients support Unix domain socket: I only know [cURL](https://curl.se/) and [HTTPie + plugin](https://httpie.io/cli). I don't like cURL's verbose syntax, I pick HTTPie for its neat syntax and colored JSON display. - The HTTPie distributed by Ubuntu repo does not include the [plugin](https://github.com/httpie/httpie-unixsocket) for Unix socket. So I have to install via [`pipx`](https://github.com/pypa/pipx). - When invoking HTTPie with `sudo`, `sudo` doesn't find `HTTPie` because the later is installed to my user home folder (*~/.local/bin*) by `pipx`. If you have the same taste as me, we will do extra setup steps to make the socket writable for our user. Concretely, we will make the socket file is under `adm` group and add ourselves to `adm` group. This `adm` group pre-exists in Debian/Ubuntu system. I'm not sure about other distro. To add ourselves to `adm` group: ```sh $ sudo adduser $USER adm ``` We will need to log out and login again to make the command about effective. Now the socket. Let's check the systemd unit file for launching Nginx Unit (*/usr/lib/systemd/system/unit.service*): ```ini [Unit] Description=NGINX Unit Wants=network-online.target After=network-online.target [Service] Type=forking PIDFile=/var/run/unit.pid EnvironmentFile=-/etc/default/unit ExecStart=/usr/sbin/unitd $DAEMON_ARGS ExecReload= [Install] WantedBy=multi-user.target ``` We can see that, this file tells systemd to read the `$DAEMON_ARGS` variable from the */etc/default/unit* and use it as arguments for the `unitd` command. Now, check what `unitd` can accept: ```shell-session $ unitd --help unit options: --version print unit version and configure options --no-daemon run unit in non-daemon mode --control ADDRESS set address of control API socket default: "unix:/var/run/control.unit.sock" --control-mode MODE set mode of the control API socket default: 0600 --control-user USER set the owner of the control API socket --control-group GROUP set the group of the control API socket --pid FILE set pid filename default: "/var/run/unit.pid" --log FILE set log filename default: "/var/log/unit.log" --modulesdir DIR set modules directory name default: "/usr/lib/unit/modules" --statedir DIR set state directory name default: "/var/lib/unit" --tmpdir DIR set tmp directory name default: "/var/tmp" --user USER set non-privileged processes to run as specified user default: "unit" --group GROUP set non-privileged processes to run as specified group default: "unit" ``` Ok, the `--control-mode` and `--control-group` options are what we want. We will create the */etc/default/unit* file with this content: ```sh DAEMON_ARGS=--control-mode 660 --control-group adm ``` Restart the systemd service and check the file permission: ```shell-session $ sudo systemctl restart unit.service ``` ![Socket file permission](https://quan-images.b-cdn.net/blogs/imgur/2026/CbdF0Jf.png) Try calling the API: ```nu ❯ https $'http+unix://('/run/control.unit.sock' | url encode -a)/status' ``` Output ```http HTTP/1.1 200 OK Connection: close Content-Length: 274 Content-Type: application/json Date: Fri, 09 May 2025 23:31:45 GMT Server: Unit/1.34.2 { "applications": {}, "connections": { "accepted": 0, "active": 0, "closed": 0, "idle": 0 }, "modules": { "python": { "lib": "/usr/lib/unit/modules/python3.12.unit.so", "version": "3.12.3" } }, "requests": { "total": 0 } } ``` It works! You may notice that the command line above is unusual. It is the Nushell syntax. I switched to Nushell to easily convert the socket file path to the URL form that HTTPie's plugin requires (percent encoding). And because there is a built-in command `http` in Nushell, I use `https` to make Nushell invoke HTTPie instead of its built-in command.