Troubleshooting

ERR_CONNECTION_REFUSED: why your server says no, and how to fix it

ERR_CONNECTION_REFUSED means the server said no: nothing listens on the port, the app is bound to 127.0.0.1 or a firewall rejects it. How to fix each one.

Published ~9 min read

Short answer: ERR_CONNECTION_REFUSED means your browser reached the IP address the domain points to and got an immediate “no” on that port. Nothing is listening on port 80 or 443, the app listens only on 127.0.0.1, or a firewall rule actively rejects the connection. Chrome and Edge word it as example.com refused to connect, Firefox as Unable to connect. Because a machine answered, the network path works, so the fix is almost always on that server: start the service, make it listen on the public address, or open the port.

diagnose in this order
# 1. From your computer: which port is refused?
curl -v http://example.com/      # "connect to ... failed: Connection refused"
curl -v https://example.com/     # same test for port 443

# 2. On the server: does anything listen on 80 or 443?
sudo ss -tlnp '( sport = :80 or sport = :443 )'

# 3. On the server: does it answer locally?
curl -I http://127.0.0.1/

# 4. Does the domain point at this server at all?
dig +short A example.com         # compare with the IP in your provider's panel

Those four commands sort almost every case. If step 2 prints nothing, the web server is not running. If step 3 works but step 1 fails, the service is bound to the wrong address or a firewall rejects the port. If step 4 shows a different IP, you are debugging the wrong machine.

What does ERR_CONNECTION_REFUSED mean exactly?

Every page load starts with a TCP handshake: the browser sends a SYN packet to port 443 (or 80) and waits. The answer decides which error you see. Chromium lists ERR_CONNECTION_REFUSED as net error -102, “a connection attempt was refused”. It is fast and definite, which is good news: something on the other end is alive.

ErrorWhat happenedWhere to look
ERR_CONNECTION_REFUSEDThe server, or a firewall in front of it, answered the connection attempt with a reset or a reject.Is the service running, and listening on the right address and port?
ERR_CONNECTION_TIMED_OUTNothing answered at all. The packets were dropped or went to the wrong place.Firewall that drops, wrong IP, server down or overloaded.
ERR_CONNECTION_RESETA connection was opened and then cut off.The app or a proxy closed it mid-request; check its logs.
Cloudflare 521Your site is proxied, and your origin refused Cloudflare instead of you.The same checks as below, run against the origin server.

If you see a timeout instead, read ERR_CONNECTION_TIMED_OUT: it has different causes and a different fix.

Cause 1: nothing is listening on the port

This is the most common cause by far. The web server crashed, failed to start after a config change, or was never enabled to start at boot, so the kernel answers every connection on that port with a reset. Check what is listening:

Debian · Ubuntu · example output
sudo ss -tlnp
State  Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0      511          0.0.0.0:80        0.0.0.0:*     users:(("nginx",pid=812,fd=6))
LISTEN 0      511             [::]:80           [::]:*     users:(("nginx",pid=812,fd=7))
LISTEN 0      511        127.0.0.1:3000      0.0.0.0:*     users:(("node",pid=1290,fd=21))

0.0.0.0 and [::] mean “every address on this machine”, so port 80 above is reachable from outside. 127.0.0.1 means the server itself only, so port 3000 refuses everyone else. If the port you need is missing entirely, the service is down. Find out why before you restart it:

nginx · use apache2 for Apache
sudo systemctl status nginx --no-pager
sudo journalctl -u nginx -n 50 --no-pager
sudo nginx -t                         # Apache: sudo apachectl configtest
sudo systemctl enable --now nginx     # start it now and at every boot

The usual reasons a web server will not start:

  • A syntax error from your last edit. nginx -t names the file and line.
  • A certificate file that does not exist, for example after you deleted a site or a renewal moved files. nginx refuses to start rather than skip one broken server block, so one bad site takes all of them down.
  • Another program already holds the port. The log says bind() to 0.0.0.0:80 failed (98: Address already in use). See Address already in use to find and stop the other process.
  • The service was never enabled, so it ran until the first reboot. systemctl is-enabled nginx tells you.

For your own app (Node, Python, Go) the same logic applies: check its systemd unit or process manager, read its last log lines, and confirm it really started on the port your reverse proxy or URL expects.

Cause 2: it works on the server but not from outside

If curl -I http://127.0.0.1:3000 works on the server but curl -I http://203.0.113.10:3000 is refused, the app is bound to the loopback address. Many frameworks do that on purpose, because their development servers are not meant to face the internet:

ServerDefaultListen on all addresses
flask run127.0.0.1:5000flask run --host=0.0.0.0
Django runserver127.0.0.1:8000python manage.py runserver 0.0.0.0:8000
Uvicorn (FastAPI)127.0.0.1:8000uvicorn main:app --host 0.0.0.0
Vite dev serverlocalhost:5173npm run dev -- --host
Next.js (next dev, next start)0.0.0.0:3000already listens on all addresses
Node http / Express listen(port)all addresses (:: or 0.0.0.0)already listens on all addresses unless you pass a host

You have two fixes. The better one for a public site is to leave the app on 127.0.0.1, run it under a production server such as gunicorn or uvicorn, and put nginx in front of it on ports 80 and 443. The app stays unreachable from outside, nginx handles TLS, and you open only the web ports. Setting up nginx as a reverse proxy with SSL walks through it. The quick fix is to make the app listen on 0.0.0.0 with the flag in the table, then open its port in the firewall.

Cause 3: HTTP works, HTTPS is refused

When http:// answers but https:// is refused, nothing listens on port 443. There are two usual histories. In the first, someone added a redirect from HTTP to HTTPS before the certificate was issued, so every visitor is sent straight to a closed port. In the second, the site never had a 443 server block, and it only half works: Chrome tries HTTPS first for an address you type and quietly falls back to HTTP when that fails. It cannot fall back once the site has sent an HSTS header, or when the domain sits on a top-level domain that browsers preload as HTTPS-only, such as .dev or .app. Those visitors see the refusal.

Debian · Ubuntu
curl -sI http://example.com/ | head -3    # a 301 to https:// here...
curl -sI https://example.com/             # ...and "Connection refused" here
sudo ss -tlnp '( sport = :443 )'          # nothing listening on 443?

# issue a certificate and let certbot add the 443 server block
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

If ss does show port 443 and only outside visitors are refused, the firewall allows 80 but not 443. That is the next cause.

Cause 4: a firewall rule rejects the port

Firewalls come in two flavours here. A drop rule stays silent and produces a timeout. A reject rule answers, and the browser shows that as refused. ufw drops by default, so a refusal from the firewall points to an explicit reject: a ufw reject rule, ufw default reject incoming, or an iptables or nftables rule with REJECT. Look for one:

Debian · Ubuntu
sudo ufw status verbose
sudo iptables -S | grep -i reject
sudo nft list ruleset | grep -i reject

Open the web ports, and allow SSH first if the firewall is not active yet:

ufw
sudo ufw allow OpenSSH         # or your custom SSH port, e.g. 2222/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status numbered      # delete a stray reject rule with: sudo ufw delete NUMBER

One firewall that rejects on purpose is fail2ban. Its stock iptables and nftables ban actions both use reject, so if a web jail banned your address you get refused while everyone else gets the site. A banned visitor may also see a timeout instead, depending on the ban action and their operating system. Run sudo fail2ban-client status, then sudo fail2ban-client status JAILNAME to see the banned list, and sudo fail2ban-client set JAILNAME unbanip 198.51.100.7 to lift the ban.

Cause 5: the Docker port is not published, or published to localhost

A container’s ports are private until you publish them with -p. Docker publishes to every host address by default, but a mapping such as -p 127.0.0.1:8080:80 is reachable from the host only. And the app inside the container must itself listen on 0.0.0.0: an app bound to the container’s own loopback cannot be reached through any mapping.

Docker
docker ps                  # PORTS: 0.0.0.0:8080->80/tcp is public,
                           # 127.0.0.1:8080->80/tcp is host-only, empty = not published
docker port mycontainer
docker logs --tail 50 mycontainer

Cause 6: the domain points at a different server

After a migration or a DNS change, the domain can still resolve to the old server, where the site no longer runs, so that machine refuses. Compare dig +short A example.com with the IP in your provider’s panel, and check www separately, because it is its own record. Also check the hosts file on your computer (/etc/hosts, or C:\Windows\System32\drivers\etc\hosts on Windows) for an old test entry that overrides DNS.

Why does localhost refuse to connect?

On your own machine, localhost refused to connect almost always means the dev server is not running on that port. Work through these:

  • Is it still running? Look at its terminal. A crash or a stopped watcher leaves the browser tab pointing at a closed port.
  • Right port? Open the exact URL the server printed when it started. Vite moves to the next free port if 5173 is taken.
  • Try 127.0.0.1 instead of localhost. localhost can resolve to the IPv6 address ::1 while the server listens on IPv4 only, or the other way round. Vite’s documentation mentions this case.
  • Testing from your phone or another PC? There, localhost is that device. Use your computer’s LAN IP and start the server with the listen-on-all flag from the table above.
  • Running in Docker or a VM? The server must listen on 0.0.0.0 inside it, and the port must be published or forwarded.
what is listening on my machine?
# Linux
sudo ss -tlnp
# macOS
lsof -iTCP -sTCP:LISTEN -nP
# Windows PowerShell
Get-NetTCPConnection -State Listen | Sort-Object LocalPort

How do I keep it from coming back?

  • Enable every service you depend on with systemctl enable --now, and give your own app units Restart=on-failure so a crash does not leave the port closed.
  • Run nginx -t before every reload. A failed reload keeps the old config running. A full restart with a broken config stops nginx.
  • Monitor from outside, on both 80 and 443. A check that runs on the server itself cannot see a firewall or bind-address problem.
  • Keep apps on 127.0.0.1 behind a reverse proxy, and open only 80, 443 and SSH. Checking open ports shows how to audit what is exposed.

If a firewall experiment locks you out of SSH as well, you still have a way in. Every VMHeaven KVM plan includes a browser-based VNC console in the panel that works even when the server’s network does not, so you can undo the rule and test again. And if you would rather start clean, an Ubuntu VPS can be reinstalled from the panel whenever you like, with Ubuntu 24.04 or Debian 13 and 12 among the images.

Frequently asked

What does ERR_CONNECTION_REFUSED mean?

Your browser reached the IP address the domain points to, and that machine actively refused the connection on the port, usually 80 or 443. Nothing is listening there, the service listens only on 127.0.0.1, or a firewall rule rejects the connection. Chrome shows it as 'refused to connect', Firefox as 'Unable to connect'.

Why does localhost refuse to connect?

The development server is not running, crashed, or runs on a different port than the URL you opened. Try http://127.0.0.1:PORT instead of localhost, because localhost can resolve to IPv6 while the server listens on IPv4 only. Inside Docker or a VM, the server must listen on 0.0.0.0 and the port must be published.

Is ERR_CONNECTION_REFUSED a problem with my computer or with the website?

If the site refuses on every network and device, the problem is on the server. If it loads over mobile data but not on your usual connection, look at proxy or VPN settings on your side, or at a fail2ban ban of your IP address on the server.

Related articles

All troubleshooting