ERR_CONNECTION_REFUSED: what it means and how to fix it
ERR_CONNECTION_REFUSED means the target machine rejected the TCP connection outright, usually because nothing is listening on that port. The request reached the host and got an immediate "no", so the network path works and the problem sits with the port or the service behind it.
What ERR_CONNECTION_REFUSED means
A TCP connection starts with a handshake: the client sends a SYN packet, and the server answers with SYN-ACK if a process is bound to that port. RFC 9293, the TCP specification, covers the other case: a segment that does not belong to any connection gets a reset in reply, and "a SYN segment that does not match an existing connection is rejected by this means." That RST is what Chrome reports as ERR_CONNECTION_REFUSED. The same RFC names an ICMP Port Unreachable message as the other fast way a connection attempt can fail; some firewalls reject with that instead of an RST, and Linux clients report it as refused too.
The RST comes from the kernel's TCP stack, not from application code, so it arrives within milliseconds. It also tells you that something at that IP address is up and answering: the host itself, with nothing bound to the port, or a firewall in front of it that rejects the port. Look at the port and the process behind it before you look at the server's power or network link.
How the error appears
Chrome shows "This site can't be reached", then "example.com refused to connect" and the code ERR_CONNECTION_REFUSED. Firefox reports "Unable to connect" and says it can't establish a connection to the server at that address. The command line gives a more exact picture:
curl -v https://example.com:8443/
* Trying 203.0.113.10:8443...
* connect to 203.0.113.10 port 8443 failed: Connection refused
* Failed to connect to example.com port 8443 after 2 ms: Connection refused
curl: (7) Failed to connect to example.com port 8443 after 2 ms: Connection refused
The output names the exact IP address and port curl tried and reports the refusal after 2 ms, far too fast for a timeout. Exit code 7 means curl got an address but could not connect to it. nc -zv example.com 8443 prints the same "Connection refused", and on Windows Test-NetConnection example.com -Port 8443 in PowerShell shows TcpTestSucceeded : False.
ERR_CONNECTION_REFUSED vs similar errors
Chrome shows all of these under the same "This site can't be reached" heading, so the code underneath is what tells them apart. The rows follow the order in which a page load meets them, from the DNS lookup to the first byte of the response. The numbers are Chromium's internal error codes.
| Error | What happened at the network or protocol level | Where to look first |
|---|---|---|
| ERR_NAME_NOT_RESOLVED (-105) | The DNS lookup returned no usable IP address, so no connection was attempted. | The domain's DNS records and the resolver the device uses. |
| ERR_ADDRESS_UNREACHABLE (-109) | The operating system or a router reported that there is no route to the IP address. | Routing on the client side: VPN routes, the local network, IPv6 setup. |
| ERR_CONNECTION_TIMED_OUT (-118) | The SYN went out and nothing came back, neither SYN-ACK nor RST, until the client gave up. | Firewalls and security groups that drop packets, whether the host is up, and the IP address DNS returns. |
| ERR_CONNECTION_REFUSED (-102) | The SYN reached the host and was rejected right away, usually with an RST. | Whether a process listens on that port and address, and firewall REJECT rules. |
| ERR_CONNECTION_RESET (-101) | The connection was open, then an RST ended it during the TLS handshake or the transfer. | Proxy, WAF, and application logs at that moment; antivirus, VPN, or filtering on the client's network. |
| ERR_EMPTY_RESPONSE (-324) | The connection opened and the request went out, but the server closed the connection without sending any data. | Server and proxy error logs for crashed workers or connections closed on purpose (nginx's return 444). |
What causes ERR_CONNECTION_REFUSED
- Nothing is listening on that port. The web server, application, or proxy that should own the port is not running, or it is configured for a different port.
- The service crashed or is restarting. A process that exited, was killed for using too much memory, or is restarting during a deploy leaves the port unbound, sometimes for only a few seconds.
- The service listens on the wrong address. A server bound to
127.0.0.1accepts connections from the machine itself and refuses everything that arrives on its public address. - A firewall rule rejects the port. A REJECT rule answers with an RST or an ICMP error, which looks the same as a closed port from outside. A DROP rule produces a timeout instead.
- The URL uses the wrong port or scheme. A typo, an old bookmark from before a port change, or an https:// address on a server that only listens on port 80.
- A local development server is not running.
localhost:3000refused almost always means the dev server was stopped, crashed on startup, or was never started in this session. - A proxy or VPN is misconfigured. A proxy pointed at a backend that is down, or a VPN client that sends requests to a port nothing serves, refuses the connection on its own behalf.
- A container or Kubernetes Service points at the wrong port. The published or target port differs from the port the application binds, so the process runs and stays healthy while every connection to it is refused.
Server-side causes and configuration
Start on the server with ss -ltnp. It lists every listening TCP socket with its address, port, and owning process, which tells you which of the cases below you have:
sudo ss -ltnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 127.0.0.1:3000 0.0.0.0:* users:(("node",pid=812,fd=19))
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=640,fd=6))
Here nginx accepts connections on port 80 on every IPv4 address, while the app on port 3000 accepts them only from the machine itself. A port that is missing from the list has no listener at all.
nginx and Apache listen directives
In nginx, listen 443 ssl; accepts IPv4 connections on all addresses, and IPv6 needs its own line. If the domain has an AAAA record and the server only listens on IPv4, IPv6 connections are refused while IPv4 works. A site that loads over http:// but is refused over https:// usually has no 443 listener at all:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com;
ssl_certificate /etc/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/example.com/privkey.pem;
}
Apply changes with sudo nginx -t && sudo systemctl reload nginx. In Apache, every port needs a Listen line (for example Listen 443 in ports.conf or httpd.conf) in addition to the <VirtualHost *:443> block; a virtual host without a matching Listen never gets a socket. Check with sudo apachectl configtest before restarting.
Node.js and development servers
The host argument decides who can connect. Omitting it makes Node.js listen on the unspecified IPv6 address :: when IPv6 is available, or 0.0.0.0 otherwise:
// Only connections from the same machine
server.listen(3000, '127.0.0.1');
// Connections on every IPv4 address
server.listen(3000, '0.0.0.0');
Many development servers bind to localhost by default, Flask's flask run and Vite among them. A phone or another computer on the same Wi-Fi is refused until you start them with flask run --host=0.0.0.0 or vite --host.
Docker port publishing
-p host:container maps a host port to a container port. The container half must be the port the application listens on inside the container, and the application must listen on 0.0.0.0 there, not 127.0.0.1:
# Host port 8080 on all host addresses -> container port 80
docker run -d --name web -p 8080:80 nginx
# Published on the host's loopback only: other machines are refused
docker run -d --name web-local -p 127.0.0.1:8080:80 nginx
# Show the ports a running container publishes
docker port web
In Compose the same mapping is ports: ["8080:80"]. When the container half or the bind address inside is wrong, outside clients are usually refused, while curl on the Docker host itself often reports Connection reset by peer or Empty reply from server instead, because Docker's proxy accepts the local connection before it fails to reach the container.
Kubernetes Services
A Service's port is what callers connect to; targetPort must be the port the process in the pod listens on. The pod's containerPort field is mostly informational unless targetPort refers to it by name:
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web
ports:
- port: 80
targetPort: 8080
If no pod behind the Service is ready (the selector matches nothing, or every pod fails its readiness probe), kube-proxy in its default iptables mode rejects connections to the Service, and callers see a refusal. Behind an ingress controller the same situation usually turns into a 503. kubectl get endpointslices -l kubernetes.io/service-name=web shows whether any ready endpoints exist, and kubectl describe pod -l app=web shows failing probes.
Firewalls
Host firewalls can refuse or drop, and the difference decides which error visitors get. In ufw, deny drops packets (the client times out) while reject answers right away (the client is refused):
sudo ufw status verbose
sudo ufw allow 443/tcp
# iptables: rules with packet counters; watch REJECT lines while you retry
sudo iptables -L INPUT -n -v --line-numbers
# nftables
sudo nft list ruleset | grep -n reject
A REJECT rule whose packet counter climbs each time you retry is the rule refusing you.
Reverse proxies and PHP-FPM
When the refusal happens between a reverse proxy and its backend, the visitor never sees ERR_CONNECTION_REFUSED. nginx logs the refusal and answers the browser with a 502 Bad Gateway instead:
2026/10/13 09:14:02 [error] 640#640: *1532 connect() failed (111: Connection refused) while connecting to upstream, client: 198.51.100.7, server: example.com, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:3000/", host: "example.com"
With PHP the same line usually means fastcgi_pass 127.0.0.1:9000; in nginx while the PHP-FPM pool listens on a Unix socket (listen = /run/php/php8.3-fpm.sock), or PHP-FPM is not running. Make both sides name the same address.
How it looks from the outside
Checks from many locations at once usually fall into one of these patterns:
- Refused from every location, instantly. The service is down, bound to the wrong address, or a REJECT rule on the server covers everyone. Start with
ss -ltnp. - Refused from some locations, accepted from others. Checkpoints reached different machines or met different rules: one of several A records points at a server without the service, a firewall rule rejects certain source ranges, or a ban list such as fail2ban rejects those addresses.
- Accepted everywhere except your own network. The refusal is local: a proxy or VPN on your device, a corporate firewall that rejects that port, or a router rule.
- Port 80 accepted, port 443 refused. The server has no HTTPS listener, and every https:// link or HSTS upgrade hits the closed port.
- Some checks refused, others time out. More than one IP address or rule is involved; compare the IP address each checkpoint connected to.
The port checker opens a TCP connection to the host and port you enter from many locations and reports for each one whether the connection was accepted, refused, or timed out.
How to fix ERR_CONNECTION_REFUSED
If you are a visitor
- Confirm the URL and port. Check for a typo, an old bookmark, or a link that still points at a port the site no longer uses.
- Check for a proxy. On Windows, open Settings > Network & internet > Proxy. On macOS, run
scutil --proxyor open the proxy settings of your network connection. In Chrome,chrome://net-internals/#proxyshows the proxy the browser is using. - Disable your VPN and reload. If the site loads afterward, the VPN configuration is the cause.
- Try a different network, such as mobile data instead of Wi-Fi, to rule out a local firewall or router rule.
- Wait a minute and retry. If the site owner is mid-deploy or restarting a service, the port may be unbound for only a few seconds.
If the refused address is localhost
- See whether anything listens on the port. On macOS or Linux run
lsof -nP -iTCP:3000 -sTCP:LISTEN; on Windows runnetstat -ano | findstr :3000, orGet-NetTCPConnection -LocalPort 3000 -State Listenin PowerShell. - Start the server and read its first lines of output. A dev server that crashes on startup (a syntax error, a missing environment variable, a port already in use) prints the reason there and then exits.
- Match the address family. If the error names
::1, the client tried IPv6 loopback while the server listens only on127.0.0.1. Usehttp://127.0.0.1:3000in the client, or let the server listen on both (in Node.js, leaving out the host argument usually does this).
If you run the site
- Confirm the process is running and bound to the expected port:
An empty result means nothing is listening; start the service or fix its configured port.sudo ss -ltnp | grep ':443' - Check the service status and logs:
A crashed or failed unit shows up here, usually with the reason it exited.systemctl status nginx journalctl -u nginx -n 50 --no-pager - Check the bind address.
127.0.0.1:443in thessoutput means outside clients are refused; listen on0.0.0.0and[::]or on the public address. - Check REJECT rules and port mappings with the firewall, Docker, and Kubernetes commands above.
- Restart and test from outside. Confirm the port appears in
ss -ltnpand that a connection from another network succeeds.
How to prevent ERR_CONNECTION_REFUSED
A process that is not running cannot report its own failure, so a crashed service or a stray REJECT rule often shows up first as refused connections in visitors' browsers. Port monitoring connects to the port from outside the network on a schedule, the same way a visitor would, and sends an alert by email, SMS, voice call, Slack, Telegram and other channels when a check fails. With checks from 300+ checkpoints in 158 cities, a refusal that affects only one region or one of several servers shows up too.
Frequently asked questions
Is ERR_CONNECTION_REFUSED the same as a firewall blocking me?
It can be. A firewall REJECT rule produces the same fast rejection as a closed port, so the browser cannot tell them apart. A DROP rule produces a timeout instead.
Why does this happen only on localhost?
Almost always because the local dev server is not running in that terminal, was stopped, or crashed on startup because of a code or configuration error. Check the terminal where you normally start it for an error message, then check which process owns the port with lsof or netstat.
Why do I get "connect ECONNREFUSED ::1"?
Since Node.js 17, DNS lookups return addresses in the order the system gives them, and on many systems localhost resolves to the IPv6 address ::1 first. A client that tries ::1 while the server listens only on 127.0.0.1 is refused. Point the client at 127.0.0.1, or make the server listen on :: as well.
Can a VPN cause ERR_CONNECTION_REFUSED?
Yes. A VPN that routes traffic through a proxy or gateway can refuse connections on the gateway's behalf if its own backend is down, even though the destination server is healthy.
Does ERR_CONNECTION_REFUSED mean the website is down for everyone?
Not necessarily. Test from a second network or run a check from multiple locations before concluding that. A single misconfigured firewall rule or a local VPN can produce the same error while the site works for everyone else.
Why does curl over http:// work while the browser shows refused?
The browser may be connecting to port 443 instead. If the site ever sent an HSTS header, Chrome rewrites http:// to https:// before connecting, so a server without an HTTPS listener is refused even though port 80 answers. chrome://net-internals/#hsts shows whether a domain has an HSTS entry.
Can an overloaded server refuse connections?
On Linux, usually not. When a listening socket's queue is full, the kernel drops new connection attempts by default instead of resetting them (the tcp_abort_on_overflow setting is off), so an overloaded server tends to produce slow connections and timeouts rather than refusals.
What is the difference between refused and reset?
Refused means the target rejected the handshake, so no connection ever existed. Reset means a connection was established and then torn down partway through, by the server, a proxy, or something else on the path.
How do I find out which process should be listening?
The application's configuration and startup log name the port it binds. If that differs from the port in the URL, change one of them so they agree, then confirm with ss -ltnp.
Sources and further reading
- Chromium net error list: the definitions of CONNECTION_REFUSED (-102) and the other codes in the table above.
- RFC 9293, Transmission Control Protocol: section 3.5.2 on when TCP sends a reset, and section 3.8.3 on how a connection attempt fails.
- Docker: port publishing and mapping: the
-psyntax and publishing to localhost only. - Kubernetes: Service: how
portandtargetPortroute traffic to pods.