How to Fix net::ERR_CONNECTION_REFUSED in Chrome
What causes Chrome's ERR_CONNECTION_REFUSED error, how to diagnose it, and how to fix it for local development servers, APIs, and production sites.
How to Fix net::ERR_CONNECTION_REFUSED in Chrome
The error in the console:
GET http://localhost:8080/api/data net::ERR_CONNECTION_REFUSED
Or in the Network tab, the request shows status (failed) with net::ERR_CONNECTION_REFUSED in red.
This means Chrome tried to open a TCP connection to the server and the server actively refused it. Not a timeout. Not a DNS failure. The machine at that address received the connection request and said no. The most common reason is straightforward: nothing is listening on that port.
This is different from ERR_CONNECTION_TIMED_OUT, where the server never responds at all (often a firewall dropping packets). ERR_CONNECTION_REFUSED means something responded, but it wasn’t a server ready to accept your request.
Common causes
1. The server isn’t running
The number one cause. You started your frontend, opened the browser, and forgot to start the backend. Or the backend crashed on startup and you didn’t notice because you were looking at the browser tab.
Fix: Start the server.
# Check if anything is listening on the port
lsof -i :8080
If that returns nothing, the port is empty. Start your server. If you see a process, check if it’s the right one. Maybe an old instance is running on a different port, or a different application grabbed 8080.
For Node.js projects, a common gotcha: the server binds to 127.0.0.1 but you’re hitting localhost, or vice versa. On most systems these are the same, but some network configurations break that assumption. If localhost:8080 gives ERR_CONNECTION_REFUSED but 127.0.0.1:8080 works, you have a DNS resolution issue on your machine.
// Explicit: bind to all interfaces
app.listen(8080, '0.0.0.0', () => {
console.log('Server running on port 8080')
})
2. Wrong port
Your server runs on 3001 but your frontend is configured to hit 3000. This happens after changing port configurations, when environment variables don’t load, or when copy-pasting from a tutorial that uses a different port than your project.
Fix: Verify the port in both places.
# What's your server actually listening on?
lsof -i -P | grep LISTEN | grep node
Check your frontend configuration:
// .env or config file
VITE_API_URL=http://localhost:8080 // is this the right port?
Environment variable issues are especially common. If VITE_API_URL isn’t set, your code might fall back to a hardcoded default that points to the wrong port. Log the URL before making the request to verify.
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:3000'
console.log('Fetching from:', apiUrl) // check this first
3. Server crashed or exited
The server started, ran for a while, then crashed. Maybe it ran out of memory, hit an unhandled exception, or a file watcher restarted it and the restart failed. Your browser still has the tab open and makes a request to a server that no longer exists.
Fix: Check your terminal or process manager for error output. If you’re using nodemon, ts-node-dev, or a similar watcher, look for a crash message followed by a failed restart.
# If you use pm2
pm2 logs
# If you run the server directly, check the terminal where you started it
# Look for a stack trace followed by the process exiting
For development, adding a simple health check endpoint helps you distinguish “server crashed” from “my request is wrong”:
app.get('/health', (req, res) => res.send('ok'))
curl http://localhost:8080/health
# If this fails, the server is down. If it succeeds, the server is up but your other route has a problem.
4. Firewall or security software blocking the port
Corporate VPNs, antivirus software, and OS-level firewalls can block local ports. This is more common on managed work machines where IT policies restrict which ports applications can bind to.
On macOS, the built-in firewall rarely causes issues for localhost connections, but third-party security tools (like Little Snitch or corporate endpoint protection) can.
On Windows, Windows Defender Firewall can block incoming connections to ports used by your dev server. You’ll get ERR_CONNECTION_REFUSED even though netstat shows the server listening.
Fix: Check your firewall settings.
# macOS: check if the firewall is blocking
sudo pfctl -sr 2>/dev/null | grep "block"
# Windows (PowerShell): check firewall rules
Get-NetFirewallRule | Where-Object { $_.Direction -eq "Inbound" -and $_.Action -eq "Block" }
For quick development, try temporarily disabling the firewall or VPN to confirm it’s the cause. If it is, add an exception for your development port rather than leaving the firewall off.
5. Docker or containerized server not exposing the port
Your server runs in a Docker container and listens on port 8080 inside the container, but you didn’t map the port to your host machine. Inside the container, everything works. Outside, nothing is listening.
Fix: Map the port in your Docker config.
# Command line
docker run -p 8080:8080 my-api
# docker-compose.yml
services:
api:
build: .
ports:
- "8080:8080"
A related issue: the server inside the container binds to 127.0.0.1 instead of 0.0.0.0. Inside Docker, 127.0.0.1 means “only connections from within this container.” To accept connections from the host machine, the server must bind to 0.0.0.0.
// Inside Docker, this is wrong:
app.listen(8080, '127.0.0.1')
// This is right:
app.listen(8080, '0.0.0.0')
6. HTTPS/HTTP mismatch
Your server runs on HTTP but you’re requesting HTTPS (or vice versa). https://localhost:8080 will get ERR_CONNECTION_REFUSED if the server only handles plain HTTP, because the TLS handshake fails immediately.
Fix: Match the protocol.
// If your server is HTTP-only:
fetch('http://localhost:8080/api/data') // not https://
// If you need HTTPS in development, set it up explicitly:
// vite: server.https option
// webpack: devServer.https option
// Next.js: experimental.https option (Next 13.5+)
Check your browser’s address bar too. Some browsers auto-upgrade http:// to https:// for certain domains (HSTS preloading). If localhost gets HSTS-upgraded, try using 127.0.0.1 directly.
Debugging workflow
When you see ERR_CONNECTION_REFUSED, follow this sequence:
- Check if the server is running. Look at the terminal where you started it. If it crashed, restart it.
- Verify the port. Run
lsof -i :PORT(macOS/Linux) ornetstat -an | findstr PORT(Windows) to confirm something is listening. - Test with curl.
curl http://localhost:8080/from the terminal removes the browser from the equation. If curl also gets “connection refused,” the problem is the server or network, not the browser. - Check Docker port mapping if applicable.
docker psshows which ports are mapped. - Check the protocol. Make sure HTTP vs HTTPS matches between client and server.
- Check the hostname. Try
127.0.0.1instead oflocalhost, or vice versa, to rule out DNS resolution weirdness.
Hushbug flags failed network requests including connection errors automatically while you browse, so they don’t get buried under other console output. Coming soon to the Chrome Web Store.