80
The plain HTTP port. Mostly a redirect to 443 these days — but closing it breaks certificate renewal, and binding it as a normal user fails before your server ever starts.
Run the line for your platform. Each one names the process holding the port and the address it bound to, which is usually the whole answer.
sudo lsof -nP -iTCP:80 -sTCP:LISTEN`-n` and `-P` keep hosts and ports numeric, `-iTCP:80` selects the protocol and port, and `-sTCP:LISTEN` hides established connections so you see only the owner. The NAME column is the bound address: `127.0.0.1:80` means local only, `*:80` means every interface and therefore reachable from the network. COMMAND and PID name the process, USER shows which account it runs as — a server that dropped privileges after binding shows a worker under `_www` or `nobody` even though root opened the socket. Empty output with exit status 1 means the port is free.
sudo ss -tlnp 'sport = :80'`-t` for TCP, `-l` for listening sockets, `-n` for numeric ports and `-p` for the owning process. Read the Local Address:Port column first: `0.0.0.0:80` is every IPv4 interface, `127.0.0.1:80` is loopback only and explains a server that answers on the box but not from outside, and `[::]:80` is the IPv6 wildcard which on most Linux hosts accepts IPv4 too. nginx shows several rows sharing one port because each worker inherits the socket — that is normal. Without `sudo` the process column is blank for processes you do not own.
sudo netstat -tlnp | grep ':80 'The net-tools form for older images and minimal containers. Same flags; the PID/Program name column needs superuser privileges for sockets you do not own and its man page warns the value is not trustworthy. The trailing space is doing real work — without it `:80` also matches `:8080`, `:8000` and `:80443`, which is the single most common way people misread this output.
netstat -ano | findstr :80`-a` shows listening ports as well as connections, `-n` keeps them numeric and `-o` adds the owning PID; `tasklist /FI "PID eq 1234"` turns that into a name. On Windows the answer is frequently PID 4, the System process, because the HTTP.sys kernel driver holds port 80 on behalf of IIS, the Web Deployment Agent or even Skype-era leftovers — killing PID 4 is not an option, so use `netsh http show servicestate` to find which application reserved it. As always, `findstr` matches substrings, so `:80` also matches `:8080`.
`nmap -Pn -p 80 example.com` reports one of three states and each has a different next step. `open` means something completed the handshake; follow up with `curl -I http://example.com/` to see whether it serves content or redirects. `closed` means the host replied with a reset, so it is reachable and nothing is listening — the server process is the problem, not the network. `filtered` means nothing came back, which points at a security group, cloud firewall or upstream ACL rather than at the host. Add `--reason` to see which of the three was actually observed instead of inferred, and `-sV` to have nmap read the response and report the server software. Scan only hosts you are responsible for.
| What you see | What it means |
|---|---|
Error: listen EACCES: permission denied 0.0.0.0:80 | Not a conflict — a privilege check. Port 80 is below 1024, so the kernel refuses the bind for an unprivileged process before it even considers whether the port is free. Run the service under its init system, grant the binary `CAP_NET_BIND_SERVICE` on Linux, listen on 8080 and put a proxy or a redirect in front, or on Linux lower `net.ipv4.ip_unprivileged_port_start` if you understand what else that permits. |
Error: listen EADDRINUSE: address already in use :::80 | Something genuinely holds the port. Run the platform probe and read the bound address before killing anything: a listener on the wildcard blocks a bind to any specific address on the same port, while two processes bound to different specific addresses coexist. On Windows the holder is often the kernel HTTP.sys driver rather than a normal process. |
curl: (7) Failed to connect to example.com port 80 after 12 ms: Couldn't connect to server | A reset came back almost instantly, so the host is reachable and nothing is listening on port 80. Either the web server is down, or it is bound to loopback, or you are hitting a load balancer that only has an HTTPS listener configured. The speed is the clue: refusals are immediate. |
curl: (28) Failed to connect to example.com port 80 after 130004 ms: Connection timed out | Nothing answered at all, so a filter dropped the packet silently. Look at the cloud security group, the host firewall, and any network ACL between you and the server. A drop takes tens of seconds where a refusal takes milliseconds, so the duration alone separates the two causes before you check anything. |
Let's Encrypt: Timeout during connect (likely firewall problem) on an http-01 renewal | The ACME server could not reach TCP port 80 from the public internet, and RFC 8555 does not let it use any other port for this challenge. Either open 80 to the world for the challenge path, or switch to the DNS-01 challenge, which validates through a TXT record and needs no inbound port at all. A renewal that fails this way is silent until the certificate expires. |
The site works from the server itself but not from another machine | Almost always a bound address rather than a firewall. The probe shows `127.0.0.1:80`, which serves only the local machine; the server needs to bind `0.0.0.0` or a specific external address. Check this before touching firewall rules — it costs one command and is the more common cause. |
IANA assigns port 80 to `http` on TCP, UDP and SCTP, referencing RFC 9110; in practice only TCP is used. What actually listens there on a modern server is rarely an application: it is a reverse proxy such as nginx, Caddy or Traefik whose entire job on this port is to answer with a redirect to HTTPS, plus one exception that catches people out. ACME, the protocol behind Let's Encrypt and most automated certificates, defines an `http-01` challenge whose validation request RFC 8555 specifies "MUST be sent to TCP port 80 on the HTTP server" — the port is fixed and not negotiable. Firewall port 80 off entirely and http-01 renewals stop working, usually silently, until a certificate expires sixty days later. The other thing to know about port 80 is that it is privileged. On Unix-like systems ports below 1024 can only be bound by a process with the right privileges, and the kernel enforces that before it looks at whether the port is free: on macOS a normal user attempting to bind 127.0.0.1:80 gets EACCES immediately. Linux exposes the boundary as the sysctl `net.ipv4.ip_unprivileged_port_start`, so read yours with `sysctl net.ipv4.ip_unprivileged_port_start` rather than assuming, and grant a specific binary the `CAP_NET_BIND_SERVICE` capability instead of running the whole server as root. This is why development servers default to 3000, 5173 or 8080 and why the same code needs a proxy or a redirect in production.
Port 80 is one of the few ports it is normal and correct to expose to the internet — but be deliberate about what it serves. Everything sent over it is readable and modifiable by anything on the path, so it should carry no login form, no session cookie, no API and no admin interface. The safe shape is a listener that answers two things: a 301 redirect to the HTTPS equivalent, and the `/.well-known/acme-challenge/` path your certificate client needs. Send `Strict-Transport-Security` from the HTTPS side so browsers stop using port 80 for your host at all after the first visit, and mark cookies `Secure` so they can never be sent over it even if something slips through. Do not assume an open port 80 is harmless because "it only redirects": the redirect target itself is attacker-modifiable on a hostile network, which is exactly what HSTS is designed to close. Finally, an internal service accidentally listening on `0.0.0.0:80` rather than loopback is one of the most common ways a staging environment becomes public — check the bound address, not just the process name.
All of these run in your browser — nothing is uploaded.
Ports people usually end up checking in the same session as 80.
Almost always permissions. lsof and ss list the socket but hide the owning process when it belongs to another user, so a server started by a system account, by a container runtime or by launchd shows up as an anonymous listener until you re-run the command with sudo. On Windows the opposite happens: netstat shows nothing at all, yet a bind still fails, which means the port sits inside a range that Hyper-V, WSL2 or Docker Desktop reserved at boot.
Look at what it is first. An orphaned dev server is safe to kill; a database with a write in flight is not, and neither is a system service that something else depends on. If the owner is a container, stop the container instead — killing the process the host sees only makes the runtime restart it. When the process is something you need, moving your own service to another port is the faster fix.
The listener bound to 127.0.0.1 rather than 0.0.0.0. The probes on every page print the bound address next to the process for exactly this reason: a loopback bind is reachable from the machine itself and from nowhere else, no matter how the firewall is configured. Many frameworks made that the default deliberately, so it is usually a flag rather than a bug. If the address is already 0.0.0.0 and it still fails, the next suspect is a host firewall or a cloud security group.
Barely. Internet-wide scanners sweep the whole port range continuously, so an unusual number buys hours, not safety, and it costs you every colleague who now has to remember it. It does cut the noise in your logs from opportunistic scans of the default port, which is a real if modest benefit. Authentication, a firewall that restricts source addresses, and not exposing the service at all are the measures that actually change the outcome.
Still stuck on port 80? browse every port in the reference — or go back to the failure message you actually got, above.