net::ERR_CONNECTION_REFUSED — Cause and Fix

Quick answer

The connection was actively refused: the machine was reachable, but nothing was listening on the host and port you asked for. It is not an HTTP error — the server never responded. Nine times out of ten the server is not running or you are pointing at the wrong port. Start the server, confirm its port, and make your URL match.

The exact error string

It shows up in the Chrome DevTools Console and Network tab, and in equivalent forms from other clients:

# Chrome console / Network tab
GET http://localhost:3000/api/users net::ERR_CONNECTION_REFUSED

# JavaScript fetch (the promise rejects with)
TypeError: Failed to fetch

# Node.js
Error: connect ECONNREFUSED 127.0.0.1:3000

# curl
curl: (7) Failed to connect to localhost port 3000: Connection refused

What it means

When a client opens a TCP connection, the target host either accepts it, ignores it, or rejects it. Connection refused is an explicit rejection: the host is up and replied, but no process is listening on that port, so the OS sends back a TCP reset. The request never reaches application code — there is no application there to reach.

This is the network-layer cause behind a browser’s TypeError: Failed to fetch: the console shows Failed to fetch, while the Network tab shows the real reason, net::ERR_CONNECTION_REFUSED.

Cause 1: The server is not running

The single most common cause. The backend crashed, was never started, or exited after an error. Nothing is bound to the port, so every connection is refused. Check the terminal where the server should be running — it may have stopped with a stack trace.

Cause 2: Wrong port or protocol

The server is running, but on a different port than the URL targets — or the scheme is wrong (http vs https):

# Server log says it is on 8080
Listening on http://localhost:8080

# But the frontend calls 3000 → refused
fetch("http://localhost:3000/api/users")   // net::ERR_CONNECTION_REFUSED

Read the server’s own startup log for the authoritative port and update the client to match.

Cause 3: Server bound to the wrong interface

A server bound only to 127.0.0.1 (localhost) accepts connections from the same machine only. Anything coming from another host, a VM, or a Docker container is refused. To accept external connections, bind to 0.0.0.0:

// Node / Express — reachable only from the same machine
app.listen(3000, "127.0.0.1");

// Reachable from other hosts and containers
app.listen(3000, "0.0.0.0");

localhost vs 127.0.0.1 (IPv4 vs IPv6)

These are not always interchangeable. On most systems localhost resolves to 127.0.0.1 (IPv4) or ::1 (IPv6), depending on the resolver. If your server listens on only one address family, a client that picks the other gets a refused connection — even though “the server is running.”

This bites Node.js and Docker setups in particular: a server bound to IPv4 127.0.0.1 will refuse a client that resolved localhost to IPv6 ::1. The fix is either to connect to 127.0.0.1 explicitly instead of localhost, or to bind the server to 0.0.0.0 (all IPv4 interfaces) or :: (dual-stack) so it accepts both families.

Cause 4: Docker container port not published

In containers, the port must be published to the host (docker run -p 3000:3000 ...), and the in-container server must bind to 0.0.0.0, not 127.0.0.1 — a server on the container’s own loopback refuses every connection from outside the container.

Cause 5: Firewall, antivirus, or proxy

Local firewalls, corporate proxies, VPNs, or security software (especially antivirus with a “web shield”) can refuse connections. This is more common on Windows and in enterprise environments. Test by temporarily disabling the firewall, VPN, or web-protection feature, or by trying from another network. Remember that a connection that is refused immediately (rather than timing out) means the host is reachable — so focus on what is, or is not, allowed to listen there.

Debugging checklist

Confirm it from the command line

Take the browser out of the picture — test the port directly:

# Does anything answer on the port?
curl -i http://localhost:3000/

# Is anything listening on the port? (macOS / Linux)
lsof -i :3000

# Windows
netstat -ano | findstr :3000

If nothing is listed as listening, that confirms the diagnosis: start the server (or fix its port). The opposite problem — the server cannot start because the port is already taken — raises EADDRINUSE: address already in use.

Frequently Asked Questions

What does net::ERR_CONNECTION_REFUSED mean?

It means the connection was actively refused at the TCP level: the machine was reachable, but nothing was listening on the host and port you requested, so the connection was rejected immediately. It is not an HTTP error — the server never got a chance to respond. The usual cause is that the server is not running, or you are pointing at the wrong port.

How do I fix ERR_CONNECTION_REFUSED on localhost?

Confirm the server is actually running and listening on the exact port in your URL. Start the backend, check its startup log for the port, and make sure the frontend points at that same port. Then verify the server is bound to the right interface: a server bound only to 127.0.0.1 will refuse connections from other hosts or containers.

What is the difference between ERR_CONNECTION_REFUSED and Failed to fetch?

net::ERR_CONNECTION_REFUSED is the low-level network error shown in Chrome's Network tab when the TCP connection is rejected. TypeError: Failed to fetch is the JavaScript error the fetch() promise rejects with when that (or any other network-layer failure) happens. They usually appear together: the console shows Failed to fetch, the Network tab shows ERR_CONNECTION_REFUSED.

Is ERR_CONNECTION_REFUSED the same as ECONNREFUSED?

They are the same underlying condition from different layers. ERR_CONNECTION_REFUSED is Chrome's name for it; ECONNREFUSED is the operating-system socket error code that Node.js, curl, and other clients report. Both mean the target host actively rejected the TCP connection because no process is listening on that port.

Why does connection refused happen with Docker?

Two common reasons. First, the container port is not published to the host (you need -p 3000:3000). Second, the server inside the container is bound to 127.0.0.1, which is the container's own loopback, so it refuses connections from outside the container. Bind to 0.0.0.0 inside the container and publish the port to fix it.

How is connection refused different from a timeout?

Connection refused is immediate and explicit: the host replied ‘nothing is listening here.’ A timeout (ERR_CONNECTION_TIMED_OUT) means the host never replied at all — often a firewall silently dropping packets, or the wrong host entirely. Refused points at the right machine with no server; a timeout points at an unreachable or filtered host.

Debugging a JSON API response?

Once the connection works, format and validate the response in your browser — nothing is uploaded to a server.

JSON Validator JSON Formatter All Error References
About the author

Pasindu Ishan is a software developer based in Sri Lanka. He builds privacy-first developer tools at JSON Dev Tools.