Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for NGINX By James Joyner IV · · 9 min read

NGINX Error Guide: '[emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)'

Fix NGINX bind() to 0.0.0.0:80 failed (98: Address already in use): find the process holding port 80, duplicate listen directives, leftover workers, and conflicting services.

  • #nginx
  • #troubleshooting
  • #errors
  • #ports

Overview

bind() to 0.0.0.0:80 failed (98: Address already in use) means NGINX tried to claim a port (here 80) that another process already holds. The kernel returns EADDRINUSE (errno 98), NGINX logs [emerg], and the master process refuses to start or reload. The service is down until the conflict is resolved.

You see this at startup or on a failed reload:

2026/06/23 16:11:03 [emerg] 5210#5210: bind() to 0.0.0.0:80 failed (98: Address already in use)
2026/06/23 16:11:03 [emerg] 5210#5210: still could not bind()

The culprit is almost always either another web server (Apache, an old NGINX instance, a container publishing port 80), or NGINX’s own config telling it to listen on the same port twice. The [emerg] severity means it is fatal — nothing serves traffic until you free the port or fix the duplicate.

Symptoms

  • systemctl start nginx fails; the service stays dead.
  • error.log / journal records bind() to 0.0.0.0:80 failed (98: Address already in use).
  • A reload fails and leaves the old config running (or nothing, if it was a cold start).
  • ss/lsof shows a different process already listening on the port.
sudo systemctl start nginx
Job for nginx.service failed because the control process exited with error code.
See "systemctl status nginx.service" and "journalctl -xeu nginx.service" for details.
sudo journalctl -u nginx --no-pager | tail -5
[emerg] 5210#5210: bind() to 0.0.0.0:80 failed (98: Address already in use)

Common Root Causes

1. Another web server already owns port 80

Apache (httpd), Caddy, or another HTTP server is bound to 80, so NGINX cannot.

sudo ss -ltnp '( sport = :80 )'
State  Recv-Q Send-Q Local Address:Port  Peer Address:Port Process
LISTEN 0      511          0.0.0.0:80         0.0.0.0:*    users:(("apache2",pid=4901,fd=4))
[emerg] 5210#5210: bind() to 0.0.0.0:80 failed (98: Address already in use)

apache2 holds port 80. Stop/disable it before starting NGINX.

2. A stale NGINX master is still running

A previous NGINX did not shut down cleanly (or a manual nginx was launched outside systemd), so an orphaned master holds the port.

ps -ef | grep '[n]ginx: master'
sudo ss -ltnp '( sport = :80 )'
root  4711  1  0 15:40 ? nginx: master process /usr/sbin/nginx
users:(("nginx",pid=4711,fd=6))
[emerg] 5210#5210: bind() to 0.0.0.0:80 failed (98: Address already in use)

An old master (pid 4711) is still bound. The systemd unit’s new master cannot bind on top of it.

3. Duplicate listen directive across server blocks/files

Two server {} blocks (often a leftover default plus your site) both listen 80 default_server; or listen 80; on the same address, and NGINX tries to bind twice.

grep -RnE 'listen\s+(80|0\.0\.0\.0:80)' /etc/nginx/conf.d/ /etc/nginx/sites-enabled/
/etc/nginx/sites-enabled/default:2:    listen 80 default_server;
/etc/nginx/sites-enabled/app:2:        listen 80 default_server;
[emerg] 5210#5210: a duplicate default server for 0.0.0.0:80 in /etc/nginx/sites-enabled/app:2

Two default_server declarations on the same port conflict. Remove or disable the leftover default site.

4. A container or another service publishes the host port

A Docker container started with -p 80:80 grabs the host’s port 80, blocking NGINX on the host.

sudo ss -ltnp '( sport = :80 )'
docker ps --format '{{.Names}}\t{{.Ports}}' | grep ':80->'
users:(("docker-proxy",pid=6033,fd=4))
web   0.0.0.0:80->80/tcp
[emerg] 5210#5210: bind() to 0.0.0.0:80 failed (98: Address already in use)

docker-proxy (container web) owns port 80. Stop the container or move NGINX to another port.

5. socket activation / systemd holding the socket

If nginx.socket (systemd socket activation) or another unit pre-binds the port, the NGINX process itself can hit EADDRINUSE.

systemctl list-units '*.socket' | grep -i nginx
sudo ss -ltnp '( sport = :80 )' | grep systemd
nginx.socket   loaded active listening
users:(("systemd",pid=1,fd=42))

Disable the conflicting socket unit or let it own the listener (do not also listen 80 in nginx.conf).

6. Binding the same port on a specific IP and on 0.0.0.0

One block listens on 0.0.0.0:80 and another on 192.0.2.10:80; the wildcard already covers that IP, so the second bind collides.

grep -RnE 'listen' /etc/nginx/conf.d/ /etc/nginx/sites-enabled/
listen 80;                 # 0.0.0.0:80
listen 192.0.2.10:80;      # collides with the wildcard above
[emerg] 5210#5210: bind() to 192.0.2.10:80 failed (98: Address already in use)

A wildcard listener plus a specific-IP listener on the same port cannot coexist.

Diagnostic Workflow

Step 1: Confirm the failure and the exact port

sudo journalctl -u nginx --no-pager | tail -5
sudo nginx -t

The [emerg] bind() to <addr>:<port> failed (98...) line names the address NGINX could not claim. nginx -t also catches duplicate default_server config errors.

Step 2: Find what already holds the port

sudo ss -ltnp '( sport = :80 )'
sudo lsof -iTCP:80 -sTCP:LISTEN -P -n

The users:(("<proc>",pid=<pid>,...)) field names the process and PID currently bound.

Step 3: Decide — kill the squatter or change NGINX

If it is a service that should not be there (old Apache, stray manual nginx):

sudo systemctl stop apache2 && sudo systemctl disable apache2
# or, for an orphaned manual master:
sudo kill <PID>   # use the master PID from step 2

If the other service is legitimate (a container, Caddy doing TLS termination), move NGINX to a different port/IP instead.

Step 4: Check the NGINX config for duplicate listens

grep -RnE 'listen\s' /etc/nginx/conf.d/ /etc/nginx/sites-enabled/
ls -l /etc/nginx/sites-enabled/

Remove leftover default sites or duplicate default_server lines. A common fix is unlinking sites-enabled/default.

Step 5: Validate and start

sudo nginx -t
sudo systemctl start nginx
sudo ss -ltnp '( sport = :80 )' | grep nginx

nginx -t must pass, then confirm NGINX (not the old process) now owns the port.

Example Root Cause Analysis

After enabling a new site, systemctl reload nginx fails and the journal shows:

[emerg] 5210#5210: bind() to 0.0.0.0:80 failed (98: Address already in use)

Checking who owns port 80:

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

Apache was installed as a dependency of another package and started on boot, claiming port 80 before NGINX. NGINX cannot bind on top of it, so the master aborts with [emerg].

Fix: stop and disable Apache, validate NGINX, and start it:

sudo systemctl stop apache2
sudo systemctl disable apache2
sudo nginx -t
sudo systemctl start nginx
sudo ss -ltnp '( sport = :80 )' | grep nginx

NGINX now binds port 80 and serves traffic.

Prevention Best Practices

  • Run a single front-door web server per port. If you need Apache or a container on 80, terminate it through NGINX (or pick distinct ports) rather than letting two services race for the bind.
  • Disable services you do not use (systemctl disable apache2) so they can never grab a port on the next reboot.
  • Keep exactly one default_server per listen address; remove the distro’s sites-enabled/default symlink when you add your own site.
  • Always start/stop NGINX through systemd, never a bare nginx command, to avoid orphaned masters that survive a restart and hold the port.
  • Run nginx -t before every reload; it catches duplicate default_server definitions before they take the service down.
  • For a quick read of a failed start, the free incident assistant can map the [emerg] bind() line and the port owner to the offending service. More fixes live in the NGINX guides.

Quick Command Reference

# Confirm the bind failure and port
sudo journalctl -u nginx --no-pager | tail -5
sudo nginx -t

# Who already holds the port?
sudo ss -ltnp '( sport = :80 )'
sudo lsof -iTCP:80 -sTCP:LISTEN -P -n

# Stop a conflicting service / orphaned master
sudo systemctl stop apache2 && sudo systemctl disable apache2
ps -ef | grep '[n]ginx: master'   # then: sudo kill <PID>

# Find duplicate listen directives
grep -RnE 'listen\s' /etc/nginx/conf.d/ /etc/nginx/sites-enabled/

# Validate and start
sudo nginx -t && sudo systemctl start nginx
sudo ss -ltnp '( sport = :80 )' | grep nginx

Conclusion

bind() ... failed (98: Address already in use) is a port-ownership conflict that stops NGINX cold with [emerg]. The usual root causes:

  1. Another web server (Apache, Caddy) already owns the port.
  2. A stale or manually launched NGINX master still holds it.
  3. Duplicate listen / default_server directives in the config.
  4. A container or other service publishes the same host port.
  5. systemd socket activation pre-binds the listener.
  6. A wildcard listener colliding with a specific-IP listener on the same port.

Identify the PID holding the port with ss/lsof, then either free it or move NGINX, fix any duplicate listen lines, and validate with nginx -t before starting. One service per port, one default_server per address.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.