What it means

Error 98 is the Linux error EADDRINUSE: a socket is already bound to that address and port. nginx needs every listen socket to start, so it exits:

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()

Through systemd you first see Job for nginx.service failed because the control process exited with error code. The lines above are in journalctl -u nginx.

Common causes

1. Another web server owns the port. Apache (apache2 or httpd), Caddy or Traefik was installed alongside nginx, or came with a package. Confirm which process listens:

sudo ss -ltnp 'sport = :80'
sudo lsof -i :80 -i :443

2. An nginx that systemd does not know about. Someone ran sudo nginx by hand, so systemctl start nginx collides with the master already running. The ss output then names nginx itself.

3. A Docker container publishes the port. -p 80:80 makes docker-proxy (or the container's own nginx) hold port 80 on the host:

docker ps --filter publish=80

4. Certbot standalone, during a renewal. certbot --standalone runs its own server on port 80 for a few seconds. An nginx restart in that window fails.

5. The config collides with itself. listen [::]:80 ipv6only=off next to a plain listen 80 opens a dual-stack socket that already covers IPv4 port 80, so nginx fails on its own second socket. ss shows nothing on port 80 while nginx is stopped. Keep listen 80 and listen [::]:80 without ipv6only=off.

How to fix

  1. Run sudo ss -ltnp 'sport = :80' and note the process and its PID.
  2. If it is another web server you do not need, stop and disable it: sudo systemctl disable --now apache2.
  3. If it is a stray nginx, stop it cleanly with sudo nginx -s quit, then start it the normal way: sudo systemctl start nginx.
  4. If it is a container, stop it or publish it on another port (-p 8080:80) and proxy to it from nginx.
  5. If both servers must run, move one of them to another port or another IP in its listen directive.
  6. Test the config and start: sudo nginx -t && sudo systemctl start nginx. nginx -t deliberately ignores error 98 when it tries the ports, so it passes even while another process, or a second listen of the same config, holds port 80. If the start still fails, go back to step 1.

Is it just you?

Check the public URL from several regions with Is my site down?: if nginx did not start, every region fails, or gets another server's page.

Get alerted next time

  • An HTTP monitor on the public URL fails when nothing answers or the status code is not the one you expect.
  • A TCP port monitor on ports 80 and 443 fails once nothing accepts connections there. If another server took the port and answers 200, only a keyword check on text from your own site notices.
  • Each failure is rechecked from your other regions before an outage opens.