# ERR_CONNECTION_REFUSED: fixes for visitors, localhost and servers

> ERR_CONNECTION_REFUSED means the machine is there and actively turned the connection away. Here are the visitor steps, the localhost diagnosis with the exact commands, and the server-side causes in the order they actually occur.

Updated 2026-10-21 · Best practices · HTML version: https://getreport.app/guides/err-connection-refused

ERR_CONNECTION_REFUSED means the connection reached a machine and the machine said no: something at that address answered, instantly and deliberately, that nothing on that port will take the call. That makes it the most informative of the errors behind Chrome's ["This site can't be reached" cover page](https://getreport.app/guides/this-site-cant-be-reached); the name lookup worked, the network route worked, and the refusal came from the far end, or from something on your own side standing in for it. For a visitor it is rarely yours to fix; for a developer it is the most common localhost error there is; for a site owner it almost always means the web server process stopped, binds the wrong address, or sits behind a firewall that rejects the port. This guide gives the short visitor pass, the localhost diagnosis with the exact commands, and the server-side causes in the order they actually occur.

## Quick answer

- **Refused is an answer, not silence.** The machine is reachable and replied that nothing accepts connections on that port. (A timeout is the opposite: nothing answered at all; different causes, different fixes.)
- **Visitor**: try the site on a phone over mobile data. If it loads there, run the local pass (proxy settings, VPN, antivirus, hosts file, DNS cache) summarised below and covered step by step in the umbrella guide.
- **Localhost**: your server is not running, is listening on a different port than you typed, or runs in Docker without the port mapped. `ss`, `lsof` or `netstat` shows in seconds what actually listens.
- **Owner**: check the service (`systemctl status nginx`), what it binds (`127.0.0.1` only reaches itself; the public needs `0.0.0.0` or the proxy), and the firewall; a *reject* rule refuses instantly, a *drop* rule times out.
- **Behind a reverse proxy**, visitors do not see refused when your app dies: they see 502, because the proxy still answers. Who got refused tells you which layer stopped.
- **Test from outside** before changing anything: an external URL check settles whether the refusal is global or local to you.

## What "connection refused" actually is

To open a connection, the browser sends a TCP connection request (a SYN packet) to the site's IP address on port 443 or 80. ERR_CONNECTION_REFUSED means the reply was a TCP reset (RST): the operating system at that address received the request, found no process listening on that port, and answered "no" by return. The refusal is active and immediate; the machine is up, the network path works, and the error usually appears in under a second.

That is the whole diagnosis in one line: **something is there, and nothing is listening on that port.** It separates refused from its look-alike, ERR_CONNECTION_TIMED_OUT, where no answer of any kind arrives and the browser gives up waiting; silence points at different suspects and gets a different fix. One other thing produces the same refusal: a firewall configured to *reject* traffic answers back on the machine's behalf, so a rule can slam the door even while your server runs fine behind it.

![Diagram: two doors with doorbells. At the left door the browser rings and an answer comes back instantly ("No.") labelled TCP RST, ERR_CONNECTION_REFUSED: someone is home and refuses. At the right door, drawn dimmed, the browser rings and nothing happens at all, labelled timeout: no answer, nobody reachable.](https://getreport.app/guides/img/err-connection-refused/diagram.svg "Refused means something IS there: the machine answers instantly that nothing listens on that port. A timeout is the silent door next to it.")

## If you're a visitor: the short pass

A genuine refusal comes from the server, so most of the time only the site's owner can act. But the "no" can also come from software on your own side (a proxy pointing at a server that no longer exists, a VPN, or an antivirus that filters web traffic) so one pass through the local suspects is worth it:

1. **Another device, another network.** Load the site on your phone with Wi-Fi off. If it fails there too, stop: the site is refusing everyone, and the rest of this list cannot help.
2. **Proxy off.** A leftover proxy setting is the classic local cause of refused: every request goes to a proxy address where nothing listens any more. On Windows 11: Settings → Network & internet → Proxy → "Use a proxy server" off. On macOS: System Settings → Network → your connection → Details… → Proxies → all off.
3. **VPN and antivirus paused.** Both sit in the middle of your connections and can refuse them. Disconnect, reload, and add an exception if that was it.
4. **Hosts file and DNS cache.** An old hosts-file line or a stale cached address sends the browser to a machine that rightly refuses. Delete the line, then flush with `ipconfig /flushdns` (Windows).

Each of these steps (with the macOS commands, router restart and DNS-server switch) is written out in full in [the shared checklist in the umbrella guide](https://getreport.app/guides/this-site-cant-be-reached); this page keeps them short because the refusal cases it owns are the next two sections.

## ERR_CONNECTION_REFUSED on localhost

`localhost refused to connect` while developing is the same error with none of the mystery: the machine that refused is yours, so you can ask it directly what is listening. There are only four usual causes.

**Nothing is running.** The dev server was never started, or the terminal that ran it is gone. Start it and read the output: the first lines name the port it actually took.

**Wrong port.** The app is on 5173 and you typed 3000, or the port was busy so the server took the next free one (`Port 3000 is in use, trying 3001 instead…`). The startup output always wins over your muscle memory.

**The server crashed after starting.** A process that exits after printing its banner leaves the port silent again. Check the terminal for the stack trace; a common one is `EADDRINUSE`, meaning an older copy of the server still holds the port.

**Docker without a port mapping.** The app listens *inside* the container, but nothing listens on your machine until the port is published with `-p 3000:3000` (or `ports: ["3000:3000"]` in compose). One more Docker trap: a server that binds `127.0.0.1` inside the container is unreachable even with the mapping; inside a container it must bind `0.0.0.0`.

One command answers "what is actually listening on this port?":

```bash
# Linux
ss -tlnp | grep 3000

# macOS
lsof -iTCP:3000 -sTCP:LISTEN -P -n
```

```
:: Windows (Command Prompt)
netstat -ano | findstr :3000
```

No output means nothing listens: start the server or fix the mapping. Output on a different port than you expected means change the URL. For Docker, `docker ps` shows the mapping in the PORTS column: you want `0.0.0.0:3000->3000/tcp`, not a bare `3000/tcp`.

## If you own the site: find what stopped answering

Visitors reporting refused means your machine (or something in front of it) is up and answering "no". Work down the layers; each has a one-command check.

**1. Is the service running?** `systemctl status nginx` (or `caddy`, `apache2`, your app's unit) answers it. If it is dead, `journalctl -u nginx -e` shows why it exited: after a crash, a failed reload with a config typo, or a reboot it was never enabled to survive (`systemctl enable nginx`).

**2. Is it listening on the right address and port?** `sudo ss -tlnp` lists every listener. Two things to read in the Local Address column:

- `127.0.0.1:8080` means the process only accepts connections from the machine itself. That is correct for an app behind a reverse proxy, and wrong for anything the public should reach directly; a service meant to be public must bind `0.0.0.0` (all addresses) or the server's public IP.
- Ports 80 and 443 must both appear for a normal HTTPS site. A listener on 80 but not 443 refuses every `https://` visit; usually a TLS config that failed to load, taking only the 443 listener down with it.

**3. Does the proxy point at the port the app actually uses?** If the app moved from port 3000 to 8080 and the proxy config still says 3000, the proxy's own connection is refused. What visitors then see depends on the layer (the next section) but the fix is the same: make the `reverse_proxy` / `proxy_pass` line match the app's real port, then reload.

**4. Is a firewall rejecting the port?** `sudo ufw status verbose` and `sudo iptables -L -n` show the rules. The verb matters: a **reject** rule (ufw `reject`, iptables `REJECT`) answers back, so visitors get refused instantly; a **drop** rule (ufw `deny`, iptables `DROP`) and most cloud security groups (AWS, Google Cloud, Azure network rules) stay silent, so those show up as timeouts instead. Refused-with-firewall therefore means a reject rule on the host itself; allow ports 80 and 443 and reload the firewall.

**5. SELinux, on RHEL-family servers.** If the web server fails to start with "permission denied" while binding a non-standard port, SELinux is refusing the bind, and nothing ends up listening. Allow the port for web servers with `semanage port -a -t http_port_t -p tcp 8080` (use `-m` instead of `-a` when the port already has a type) and start the service again.

## Behind a reverse proxy, "refused" turns into 502

Most production sites put a reverse proxy (nginx, Caddy, a load balancer) in front of the application, and that changes who sees the refusal. The visitor's browser only connects to the proxy; the proxy opens its own connection to the app. So when the *app* crashes, the proxy's connection is the one refused, and the proxy reports it to the visitor as a [502 bad gateway](https://getreport.app/guides/502-bad-gateway): the classic `connect() failed (111: Connection refused) while connecting to upstream` in the nginx error log. The visitor only sees ERR_CONNECTION_REFUSED when the *outermost* listener is gone: the proxy itself stopped, or the firewall rejects the port. ([What a reverse proxy does](https://getreport.app/guides/what-is-a-reverse-proxy) walks through this two-connection picture.)

The same logic extends to a CDN. With Cloudflare proxying your site, a dead origin does not show visitors a browser error at all: Cloudflare answers with its own error 521, "web server is down", which is Cloudflare saying your origin refused *it*.

Read the error as a pointer, outside in: browser shows refused → the outermost layer (proxy, or the firewall in front of it) is down. Browser shows 502 or 521 → the outer layer is fine and the thing *behind* it refused. Each layer's refusal surfaces one layer further out.

## Check it from outside before you change anything

Your own machine is the worst place to judge a refusal: your hosts file, VPN, office network or an SSH tunnel can make the site look up when it is down, or down when it is up. Settle it with a request from somewhere that has none of your context:

> **Free tool:** [Bulk redirect checker: status of 1,000 URLs](https://getreport.app/tools/bulk-url-checker): Free bulk redirect checker. Paste up to 1,000 URLs and get each status code, final URL, redirect hops, noindex and canonical in one table, with CSV export.

Paste your home page and a few key URLs: they are requested from getReport's servers, an outside view with no cache or VPN of yours attached, and each comes back with its status code and response time, or an **unreachable** label when no connection could be made. Every URL unreachable from outside while the site works at your desk means the refusal is real and outside help will not see your site until you fix the server; every URL answering 200 means the complaining visitor's own proxy, VPN or network is refusing, and the visitor pass above is what to send them. (The checker reaches public addresses only; localhost and private ranges are skipped by its safety guard, so the localhost section's commands remain the tool for dev servers.)

## Questions people ask

### What does ERR_CONNECTION_REFUSED mean?

It means the machine at the site's address received the browser's connection request and actively answered "no" (a TCP reset) because no process is accepting connections on that port. DNS worked and the machine is reachable; what is missing is a listening server. Typical causes: the web server process stopped, it listens on a different port or only on 127.0.0.1, or a firewall rule rejects the port. It is not a timeout; something answered, instantly.

### How do I fix ERR_CONNECTION_REFUSED on localhost?

Start the server, then check the port. Run `ss -tlnp | grep 3000` (Linux), `lsof -iTCP:3000 -sTCP:LISTEN` (macOS) or `netstat -ano | findstr :3000` (Windows): no output means nothing listens; the dev server is not running or crashed after starting. Output on another port means the URL is wrong; dev servers often take the next free port. In Docker, publish the port (`-p 3000:3000`) and make the app bind `0.0.0.0` inside the container, not `127.0.0.1`.

### Can a firewall cause ERR_CONNECTION_REFUSED?

Yes: when the rule *rejects* rather than *drops*. A reject rule (iptables `REJECT`, ufw `reject`) answers the connection attempt on the server's behalf, so the browser gets an instant refusal even though the web server runs fine behind it. A drop rule and most cloud security groups discard the packet silently, which shows up as a timeout instead. So refused plus a firewall means: look for a reject rule on the host and allow ports 80 and 443.

### Is ERR_CONNECTION_REFUSED a server problem or my problem?

Test from a second network and you will know in a minute: load the site on a phone over mobile data, or run an outside URL check. If it fails there too, it is the server; the process is down, misbound or firewalled, and only the site's owner can act. If it works everywhere but your machine, something of yours is refusing: a stale proxy setting, a VPN, antivirus web filtering, or an old hosts-file entry pointing the name at the wrong machine.

### Why do I get connection refused on one port but not another?

Because listening happens per port, not per machine. Each server process binds the specific ports it was configured for, and every other port on the same machine answers connection attempts with a refusal. So `http://` on port 80 can work while `https://` on 443 is refused (often a TLS config that failed, taking down only the 443 listener) or an app answers on 8080 while the proxy still dials 3000. Firewall rules are per-port too. `ss -tlnp` on the server lists exactly which ports have a listener.
