Skip to content

Run nginx workers as an unprivileged user

Severity: mediumApplies to: nginx 1.25+Applies to: nginx 1.30 / 1.31Applies to: freenginx
The fix/etc/nginx/nginx.conf
user www-data; # Debian, Ubuntu
# user nginx; # RHEL, Rocky, Alma

Distribution packages already do this. The page matters for hand-built installs, containers, and configs where someone set user root; to fix a permissions problem.

Why it matters — and the part that confuses people

Section titled “Why it matters — and the part that confuses people”

nginx runs as two kinds of process, and only one of them should worry you:

  • The master starts as root. It has to: binding ports below 1024 requires privilege, and so does reading a TLS private key that is correctly locked down. It does no request handling.
  • The workers are what actually parse requests, terminate TLS and talk to your application. They drop to the user you configure.

So ps showing root for one nginx process is normal and not a finding. Every other line showing root is.

That distinction matters because the workers are where the risk is. nginx has shipped memory-safety bugs in request-handling code — the 1.30.4/1.31.3 releases in 2026 fixed a buffer overflow in map with regex, a memory disclosure in the slice module, and a use-after-free. Those are worker-side. Whether such a bug is a contained compromise or a root shell is decided entirely by the user directive.

Don’t use root to fix a permissions problem

Section titled “Don’t use root to fix a permissions problem”

The usual road to user root; is a static file nginx can’t read, or a socket it can’t connect to. Root makes the error go away instantly.

Fix the permission instead:

Terminal window
sudo chown -R www-data:www-data /var/www/site
sudo chmod 640 /etc/nginx/ssl/site.key # readable by root; master reads it before dropping

The key file is the one that trips people. The master reads it at startup while still root, so it does not need to be readable by www-data — and it shouldn’t be. A key readable by the worker user is a key readable by anything that compromises a worker.

Cheap additional containment for the same bug:

/etc/systemd/system/nginx.service.d/hardening.conf
[Service]
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/log/nginx /var/lib/nginx /run

ProtectSystem=strict makes the filesystem read-only apart from the paths you name, so even a worker running as root couldn’t write to /etc.

Note NoNewPrivileges is compatible with binding port 80 here — the master binds before dropping privileges, so nothing needs to gain privilege later.