Fix nginx: [emerg] bind() to 0.0.0.0:80 failed (13: Permission denied)

beginnerโšก Nginx2026-03-23| Linux (Ubuntu, Debian, CentOS, RHEL), Nginx 1.x, running as non-root user or missing capabilities

Error Message

nginx: [emerg] bind() to 0.0.0.0:80 failed (13: Permission denied)
#nginx#bind#permission#port#emerg

The Error

You start or reload Nginx and immediately see this in the terminal or logs:

nginx: [emerg] bind() to 0.0.0.0:80 failed (13: Permission denied)
nginx: [emerg] bind() to 0.0.0.0:443 failed (13: Permission denied)
nginx: [emerg] still could not bind()

Nginx exits. No traffic gets served. The whole thing just stops.

Why This Happens

Linux treats ports below 1024 as privileged. Only root โ€” or a process explicitly granted CAP_NET_BIND_SERVICE โ€” can bind them. Normally, Nginx starts as root, grabs ports 80 and 443, then forks worker processes under a restricted user like www-data or nginx. This error means the master process never reached that binding step. Common culprits:

  • Nginx was launched directly as a non-root user
  • The binary is missing the CAP_NET_BIND_SERVICE capability
  • A systemd service override runs Nginx as an unprivileged user right from the start
  • A container or sandbox is blocking privileged port access

Worth noting: this is not the same as Address already in use. The port is free here โ€” Nginx just doesn't have permission to claim it.

Step-by-Step Fix

1. Check how Nginx is being started

Before touching anything, find out what user is actually running Nginx:

ps aux | grep nginx
# or inspect the systemd unit:
systemctl cat nginx

If the User= line in the service file points to a non-root account with no capabilities set, that's your problem right there.

2. Start Nginx as root (the normal approach)

Nginx is designed to start as root. The master process binds the ports first, then immediately drops down to the worker user defined in nginx.conf. No special setup needed.

sudo systemctl start nginx
# or directly:
sudo nginx

If this works, your original startup skipped sudo. That's the whole issue.

3. Grant CAP_NET_BIND_SERVICE to the binary (rootless setups)

Running Nginx without root by design? Grant it only the capability it actually needs:

sudo setcap 'cap_net_bind_service=+ep' /usr/sbin/nginx

Confirm the capability is set:

getcap /usr/sbin/nginx
# Expected:
# /usr/sbin/nginx cap_net_bind_service=+ep

Done. Nginx can now bind port 80 and 443 without root privileges.

4. Fix a misconfigured systemd service

Sometimes the issue is in the unit file itself โ€” systemd is dropping privileges before Nginx even tries to bind. Override the service to fix it:

sudo systemctl edit nginx

Set the master process to start as root:

[Service]
User=root

Apply the change:

sudo systemctl daemon-reload
sudo systemctl restart nginx

5. Docker / container environments

Containers often block privileged ports by default. You have two clean options:

Option A โ€” Keep Nginx listening on a high port (e.g., 8080) inside the container, and let Docker map it to port 80 on the host:

docker run -p 80:8080 my-nginx-image

Update your Nginx config inside the container:

listen 8080;

Option B โ€” Add the NET_BIND_SERVICE capability to the container directly:

docker run --cap-add=NET_BIND_SERVICE -p 80:80 my-nginx-image

Verify the Fix

After applying any of the fixes above, check that Nginx is actually running:

sudo systemctl status nginx

Look for active (running). Then confirm it's listening on the right ports:

sudo ss -tlnp | grep nginx
# or:
sudo netstat -tlnp | grep :80

Expected output:

LISTEN  0  511  0.0.0.0:80   0.0.0.0:*  users:(("nginx",pid=1234,fd=6))

Finally, send a quick request to make sure traffic is actually flowing:

curl -I http://localhost

Tips

  • Don't confuse this with 403 Forbidden โ€” this error happens at startup, at the OS socket level. It has nothing to do with your web root or file permissions.
  • Nginx package upgrades wipe the setcap setting. The capability is tied to the binary file. Every time a package update replaces /usr/sbin/nginx, you'll need to re-run setcap. Consider adding it to a post-install hook or a deploy script.
  • Check SELinux or AppArmor first on RHEL/CentOS. Even root can be blocked on enforcing SELinux systems. Run getenforce to see the current mode. If it's Enforcing, use semanage port -a -t http_port_t -p tcp 80 to explicitly allow the port.
  • If you're sorting out related file permission issues โ€” like chmod on socket files or log directories โ€” the Unix Permissions Calculator on ToolCraft is handy. It lets you build chmod values visually without memorizing octal notation.

Related Error Notes