Fix Nginx bind() to 0.0.0.0:80 Failed (Address Already in Use)

Fix nginx bind() to 0.0.0.0:80 failed (98: Address already in use) on a Django server: find the process holding port 80 or 443, stop Apache or a stray Nginx, and fix duplicate listen directives and Docker port clashes.

Problem statement

Nginx will not start:

nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
nginx: [emerg] still could not bind()

Another process already holds port 80 (or 443), so Nginx cannot listen there. On a fresh server this is usually Apache, installed by default on some images or pulled in as a dependency. On an existing server it is often an Nginx master that systemd lost track of, a Docker container publishing port 80, or Caddy or Traefik left over from an earlier attempt.

Who is holding port 80?Nginx startstries to bind :80Port takenby another processFind ownerss -ltnpStop or move itthen start NginxOnly one process can listen on an address and port, so find the owner before changing anything.
  1. Find the owner: sudo ss -ltnp 'sport = :80' (and :443).
  2. If it is Apache: sudo systemctl disable --now apache2 (or httpd).
  3. If it is an orphaned Nginx: sudo pkill nginx then sudo systemctl start nginx.
  4. If it is Docker: docker ps --filter publish=80 and remove or remap that container.
  5. If no other process holds it, look for two listen lines for the same address in your Nginx config.

Step-by-step solution

1. Identify the process

sudo ss -ltnp 'sport = :80'
sudo ss -ltnp 'sport = :443'
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("apache2",pid=812,fd=4))

The users: column names the program and PID. On older systems sudo lsof -i :80 gives the same information.

2. Apache is running

Ubuntu and Debian images with a "LAMP" history, or packages such as php and some monitoring tools, install Apache and start it on port 80:

sudo systemctl disable --now apache2
sudo systemctl start nginx

If you do not need Apache at all, remove it: sudo apt purge apache2. On RHEL-family systems the service is httpd. If you deliberately run Apache with mod_wsgi for Django, you do not also need Nginx on the same port; choose one.

3. An orphaned Nginx process

If ss shows nginx but systemctl status nginx says inactive or failed, an Nginx master was started outside systemd (for example by running sudo nginx by hand) and systemd cannot manage it:

ps -ef | grep [n]ginx
sudo nginx -s quit          # graceful stop of the unmanaged master
sudo systemctl start nginx

From then on, only use systemctl to start and stop it.

what ss -ltnp shows and what to doOwner of :80Fixapache2 or httpddisable --now, or purge if unusednginx, but systemd says inactivenginx -s quit, thenstart via systemctldocker-proxycontainer publishes80; remove or remap itcaddy or traefikleftover proxy; stop itor let it be the proxynothing listedduplicate listen linesin your own configTwo web servers cannot share a port; pick one to own 80 and 443django-deployment.com
The process name from ss tells you which fix applies.

4. A Docker container publishes the port

docker ps --format '{{.Names}}\t{{.Ports}}' | grep -E ':80->|:443->'

If a Compose stack publishes 80:80 for its own Nginx or Traefik, either let that container be the public proxy and stop host Nginx, or change the mapping (8080:80) and proxy to it from host Nginx. Also check for containers set to restart: always that come back after you stop them.

5. Duplicate listen directives in your config

If nothing else holds the port and Nginx still fails on start, the configuration may ask to bind the same socket twice with conflicting options, for example listen 80; in one block and listen 0.0.0.0:80 reuseport; in another, or listen [::]:80 ipv6only=off; alongside listen 80;. Review every listen line:

sudo nginx -T 2>/dev/null | grep -n "listen"

Options such as default_server, reuseport, and ipv6only may appear on only one listen per address and port.

free the port, then start cleanly1Find the ownerss -ltnp sport = :802Stop it properlysystemctl, docker, or nginx -squit3Disable it at bootor it returns after reboot4Test the confignginx -t5Start Nginx with systemdsystemctl enable --now nginxDisabling the other service is what keeps the fix after a rebootdjango-deployment.com
Stopping without disabling brings the conflict back on the next boot.

Verification check:

sudo ss -ltnp 'sport = :80'
sudo systemctl is-enabled nginx apache2 2>/dev/null
curl -sI http://localhost | head -1

Port 80 should belong to nginx, Apache should be disabled or absent, and a reboot should bring up Nginx alone.

Explanation

A TCP listening socket is unique per address and port. When Nginx starts, the master process binds every address in its listen directives before starting workers; if any bind fails, it retries a few times ("still could not bind") and exits. Reloads do not hit this error because the master already holds the sockets and passes them to new workers, which is why the error appears at boot or on restart but not on reload.

Edge cases / notes

  • Ports below 1024 need root or CAP_NET_BIND_SERVICE. Nginx's master runs as root for this reason; a permission error (13: Permission denied) is a different problem.
  • IPv6 and IPv4. On Linux, listen [::]:80 with ipv6only=off also takes IPv4 port 80, which conflicts with a separate listen 80. The default ipv6only=on avoids that.
  • Snap or alternative installs of Nginx can run alongside the distribution package. Check snap list and which -a nginx.
  • Control panels such as Plesk or cPanel manage their own web server on 80 and 443; do not install a second Nginx alongside them.

Next steps

FAQ

How do I see what is using port 80?

Run sudo ss -ltnp 'sport = :80'. The users: field shows the process name and PID.

Can Nginx and Apache both run on one server?

Yes, on different ports, for example Apache on 8080 behind Nginx. They cannot both listen on 80.

Why does reload work but restart fail?

Reload keeps the existing listening sockets. Restart releases and rebinds them, which fails if another process took the port.

Not directly. Gunicorn listens on its own socket or port, not on 80, unless misconfigured.

Keep going